Merge branch 'main' into main
This commit is contained in:
commit
bde83b31b0
36
.github/labeler.yml
vendored
36
.github/labeler.yml
vendored
@ -138,6 +138,42 @@
|
|||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "src/cli/**"
|
- "src/cli/**"
|
||||||
|
|
||||||
|
"commands":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/commands/**"
|
||||||
|
|
||||||
|
"scripts":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "scripts/**"
|
||||||
|
|
||||||
|
"docker":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "Dockerfile"
|
||||||
|
- "Dockerfile.*"
|
||||||
|
- "docker-compose.yml"
|
||||||
|
- "docker-setup.sh"
|
||||||
|
- ".dockerignore"
|
||||||
|
- "scripts/**/*docker*"
|
||||||
|
- "scripts/**/Dockerfile*"
|
||||||
|
- "scripts/sandbox-*.sh"
|
||||||
|
- "src/agents/sandbox*.ts"
|
||||||
|
- "src/commands/sandbox*.ts"
|
||||||
|
- "src/cli/sandbox-cli.ts"
|
||||||
|
- "src/docker-setup.test.ts"
|
||||||
|
- "src/config/**/*sandbox*"
|
||||||
|
- "docs/cli/sandbox.md"
|
||||||
|
- "docs/gateway/sandbox*.md"
|
||||||
|
- "docs/install/docker.md"
|
||||||
|
- "docs/multi-agent-sandbox-tools.md"
|
||||||
|
|
||||||
|
"agents":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/agents/**"
|
||||||
|
|
||||||
"security":
|
"security":
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
|
|||||||
2
.github/workflows/auto-response.yml
vendored
2
.github/workflows/auto-response.yml
vendored
@ -3,7 +3,7 @@ name: Auto response
|
|||||||
on:
|
on:
|
||||||
issues:
|
issues:
|
||||||
types: [labeled]
|
types: [labeled]
|
||||||
pull_request:
|
pull_request_target:
|
||||||
types: [labeled]
|
types: [labeled]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
@ -8,6 +8,7 @@ Status: unreleased.
|
|||||||
### Changes
|
### Changes
|
||||||
- Agents: honor tools.exec.safeBins in exec allowlist checks. (#2281)
|
- Agents: honor tools.exec.safeBins in exec allowlist checks. (#2281)
|
||||||
- Docs: tighten Fly private deployment steps. (#2289) Thanks @dguido.
|
- Docs: tighten Fly private deployment steps. (#2289) Thanks @dguido.
|
||||||
|
- Docs: add migration guide for moving to a new machine. (#2381)
|
||||||
- Gateway: warn on hook tokens via query params; document header auth preference. (#2200) Thanks @YuriNachos.
|
- Gateway: warn on hook tokens via query params; document header auth preference. (#2200) Thanks @YuriNachos.
|
||||||
- Gateway: add dangerous Control UI device auth bypass flag + audit warnings. (#2248)
|
- Gateway: add dangerous Control UI device auth bypass flag + audit warnings. (#2248)
|
||||||
- Doctor: warn on gateway exposure without auth. (#2016) Thanks @Alex-Alaniz.
|
- Doctor: warn on gateway exposure without auth. (#2016) Thanks @Alex-Alaniz.
|
||||||
|
|||||||
@ -201,7 +201,7 @@ For ad-hoc workflows, call Lobster directly.
|
|||||||
|
|
||||||
- Lobster runs as a **local subprocess** (`lobster` CLI) in tool mode and returns a **JSON envelope**.
|
- Lobster runs as a **local subprocess** (`lobster` CLI) in tool mode and returns a **JSON envelope**.
|
||||||
- If the tool returns `needs_approval`, you resume with a `resumeToken` and `approve` flag.
|
- If the tool returns `needs_approval`, you resume with a `resumeToken` and `approve` flag.
|
||||||
- The tool is an **optional plugin**; you must allowlist `lobster` in `tools.allow`.
|
- The tool is an **optional plugin**; enable it additively via `tools.alsoAllow: ["lobster"]` (recommended).
|
||||||
- If you pass `lobsterPath`, it must be an **absolute path**.
|
- If you pass `lobsterPath`, it must be an **absolute path**.
|
||||||
|
|
||||||
See [Lobster](/tools/lobster) for full usage and examples.
|
See [Lobster](/tools/lobster) for full usage and examples.
|
||||||
|
|||||||
@ -401,7 +401,7 @@ remote mode, remember the gateway host owns the session store and workspace.
|
|||||||
up **memory + bootstrap files**, but **not** session history or auth. Those live
|
up **memory + bootstrap files**, but **not** session history or auth. Those live
|
||||||
under `~/.clawdbot/` (for example `~/.clawdbot/agents/<agentId>/sessions/`).
|
under `~/.clawdbot/` (for example `~/.clawdbot/agents/<agentId>/sessions/`).
|
||||||
|
|
||||||
Related: [Where things live on disk](/help/faq#where-does-clawdbot-store-its-data),
|
Related: [Migrating](/install/migrating), [Where things live on disk](/help/faq#where-does-clawdbot-store-its-data),
|
||||||
[Agent workspace](/concepts/agent-workspace), [Doctor](/gateway/doctor),
|
[Agent workspace](/concepts/agent-workspace), [Doctor](/gateway/doctor),
|
||||||
[Remote mode](/gateway/remote).
|
[Remote mode](/gateway/remote).
|
||||||
|
|
||||||
|
|||||||
@ -177,4 +177,5 @@ Then open a new terminal (or `rehash` in zsh / `hash -r` in bash).
|
|||||||
## Update / uninstall
|
## Update / uninstall
|
||||||
|
|
||||||
- Updates: [Updating](/install/updating)
|
- Updates: [Updating](/install/updating)
|
||||||
|
- Migrate to a new machine: [Migrating](/install/migrating)
|
||||||
- Uninstall: [Uninstall](/install/uninstall)
|
- Uninstall: [Uninstall](/install/uninstall)
|
||||||
|
|||||||
190
docs/install/migrating.md
Normal file
190
docs/install/migrating.md
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
---
|
||||||
|
summary: "Move (migrate) a Clawdbot install from one machine to another"
|
||||||
|
read_when:
|
||||||
|
- You are moving Clawdbot to a new laptop/server
|
||||||
|
- You want to preserve sessions, auth, and channel logins (WhatsApp, etc.)
|
||||||
|
---
|
||||||
|
# Migrating Clawdbot to a new machine
|
||||||
|
|
||||||
|
This guide migrates a Clawdbot Gateway from one machine to another **without redoing onboarding**.
|
||||||
|
|
||||||
|
The migration is simple conceptually:
|
||||||
|
|
||||||
|
- Copy the **state directory** (`$CLAWDBOT_STATE_DIR`, default: `~/.clawdbot/`) — this includes config, auth, sessions, and channel state.
|
||||||
|
- Copy your **workspace** (`~/clawd/` by default) — this includes your agent files (memory, prompts, etc.).
|
||||||
|
|
||||||
|
But there are common footguns around **profiles**, **permissions**, and **partial copies**.
|
||||||
|
|
||||||
|
## Before you start (what you are migrating)
|
||||||
|
|
||||||
|
### 1) Identify your state directory
|
||||||
|
|
||||||
|
Most installs use the default:
|
||||||
|
|
||||||
|
- **State dir:** `~/.clawdbot/`
|
||||||
|
|
||||||
|
But it may be different if you use:
|
||||||
|
|
||||||
|
- `--profile <name>` (often becomes `~/.clawdbot-<profile>/`)
|
||||||
|
- `CLAWDBOT_STATE_DIR=/some/path`
|
||||||
|
|
||||||
|
If you’re not sure, run on the **old** machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clawdbot status
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for mentions of `CLAWDBOT_STATE_DIR` / profile in the output. If you run multiple gateways, repeat for each profile.
|
||||||
|
|
||||||
|
### 2) Identify your workspace
|
||||||
|
|
||||||
|
Common defaults:
|
||||||
|
|
||||||
|
- `~/clawd/` (recommended workspace)
|
||||||
|
- a custom folder you created
|
||||||
|
|
||||||
|
Your workspace is where files like `MEMORY.md`, `USER.md`, and `memory/*.md` live.
|
||||||
|
|
||||||
|
### 3) Understand what you will preserve
|
||||||
|
|
||||||
|
If you copy **both** the state dir and workspace, you keep:
|
||||||
|
|
||||||
|
- Gateway configuration (`clawdbot.json`)
|
||||||
|
- Auth profiles / API keys / OAuth tokens
|
||||||
|
- Session history + agent state
|
||||||
|
- Channel state (e.g. WhatsApp login/session)
|
||||||
|
- Your workspace files (memory, skills notes, etc.)
|
||||||
|
|
||||||
|
If you copy **only** the workspace (e.g., via Git), you do **not** preserve:
|
||||||
|
|
||||||
|
- sessions
|
||||||
|
- credentials
|
||||||
|
- channel logins
|
||||||
|
|
||||||
|
Those live under `$CLAWDBOT_STATE_DIR`.
|
||||||
|
|
||||||
|
## Migration steps (recommended)
|
||||||
|
|
||||||
|
### Step 0 — Make a backup (old machine)
|
||||||
|
|
||||||
|
On the **old** machine, stop the gateway first so files aren’t changing mid-copy:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clawdbot gateway stop
|
||||||
|
```
|
||||||
|
|
||||||
|
(Optional but recommended) archive the state dir and workspace:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Adjust paths if you use a profile or custom locations
|
||||||
|
cd ~
|
||||||
|
tar -czf clawdbot-state.tgz .clawdbot
|
||||||
|
|
||||||
|
tar -czf clawd-workspace.tgz clawd
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have multiple profiles/state dirs (e.g. `~/.clawdbot-main`, `~/.clawdbot-work`), archive each.
|
||||||
|
|
||||||
|
### Step 1 — Install Clawdbot on the new machine
|
||||||
|
|
||||||
|
On the **new** machine, install the CLI (and Node if needed):
|
||||||
|
|
||||||
|
- See: [Install](/install)
|
||||||
|
|
||||||
|
At this stage, it’s OK if onboarding creates a fresh `~/.clawdbot/` — you will overwrite it in the next step.
|
||||||
|
|
||||||
|
### Step 2 — Copy the state dir + workspace to the new machine
|
||||||
|
|
||||||
|
Copy **both**:
|
||||||
|
|
||||||
|
- `$CLAWDBOT_STATE_DIR` (default `~/.clawdbot/`)
|
||||||
|
- your workspace (default `~/clawd/`)
|
||||||
|
|
||||||
|
Common approaches:
|
||||||
|
|
||||||
|
- `scp` the tarballs and extract
|
||||||
|
- `rsync -a` over SSH
|
||||||
|
- external drive
|
||||||
|
|
||||||
|
After copying, ensure:
|
||||||
|
|
||||||
|
- Hidden directories were included (e.g. `.clawdbot/`)
|
||||||
|
- File ownership is correct for the user running the gateway
|
||||||
|
|
||||||
|
### Step 3 — Run Doctor (migrations + service repair)
|
||||||
|
|
||||||
|
On the **new** machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clawdbot doctor
|
||||||
|
```
|
||||||
|
|
||||||
|
Doctor is the “safe boring” command. It repairs services, applies config migrations, and warns about mismatches.
|
||||||
|
|
||||||
|
Then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clawdbot gateway restart
|
||||||
|
clawdbot status
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common footguns (and how to avoid them)
|
||||||
|
|
||||||
|
### Footgun: profile / state-dir mismatch
|
||||||
|
|
||||||
|
If you ran the old gateway with a profile (or `CLAWDBOT_STATE_DIR`), and the new gateway uses a different one, you’ll see symptoms like:
|
||||||
|
|
||||||
|
- config changes not taking effect
|
||||||
|
- channels missing / logged out
|
||||||
|
- empty session history
|
||||||
|
|
||||||
|
Fix: run the gateway/service using the **same** profile/state dir you migrated, then rerun:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clawdbot doctor
|
||||||
|
```
|
||||||
|
|
||||||
|
### Footgun: copying only `clawdbot.json`
|
||||||
|
|
||||||
|
`clawdbot.json` is not enough. Many providers store state under:
|
||||||
|
|
||||||
|
- `$CLAWDBOT_STATE_DIR/credentials/`
|
||||||
|
- `$CLAWDBOT_STATE_DIR/agents/<agentId>/...`
|
||||||
|
|
||||||
|
Always migrate the entire `$CLAWDBOT_STATE_DIR` folder.
|
||||||
|
|
||||||
|
### Footgun: permissions / ownership
|
||||||
|
|
||||||
|
If you copied as root or changed users, the gateway may fail to read credentials/sessions.
|
||||||
|
|
||||||
|
Fix: ensure the state dir + workspace are owned by the user running the gateway.
|
||||||
|
|
||||||
|
### Footgun: migrating between remote/local modes
|
||||||
|
|
||||||
|
- If your UI (WebUI/TUI) points at a **remote** gateway, the remote host owns the session store + workspace.
|
||||||
|
- Migrating your laptop won’t move the remote gateway’s state.
|
||||||
|
|
||||||
|
If you’re in remote mode, migrate the **gateway host**.
|
||||||
|
|
||||||
|
### Footgun: secrets in backups
|
||||||
|
|
||||||
|
`$CLAWDBOT_STATE_DIR` contains secrets (API keys, OAuth tokens, WhatsApp creds). Treat backups like production secrets:
|
||||||
|
|
||||||
|
- store encrypted
|
||||||
|
- avoid sharing over insecure channels
|
||||||
|
- rotate keys if you suspect exposure
|
||||||
|
|
||||||
|
## Verification checklist
|
||||||
|
|
||||||
|
On the new machine, confirm:
|
||||||
|
|
||||||
|
- `clawdbot status` shows the gateway running
|
||||||
|
- Your channels are still connected (e.g. WhatsApp doesn’t require re-pair)
|
||||||
|
- The dashboard opens and shows existing sessions
|
||||||
|
- Your workspace files (memory, configs) are present
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Doctor](/gateway/doctor)
|
||||||
|
- [Gateway troubleshooting](/gateway/troubleshooting)
|
||||||
|
- [Where does Clawdbot store its data?](/help/faq#where-does-clawdbot-store-its-data)
|
||||||
@ -158,7 +158,19 @@ If you want to use a custom binary location, pass an **absolute** `lobsterPath`
|
|||||||
|
|
||||||
## Enable the tool
|
## Enable the tool
|
||||||
|
|
||||||
Lobster is an **optional** plugin tool (not enabled by default). Allow it per agent:
|
Lobster is an **optional** plugin tool (not enabled by default).
|
||||||
|
|
||||||
|
Recommended (additive, safe):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tools": {
|
||||||
|
"alsoAllow": ["lobster"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or per-agent:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -167,7 +179,7 @@ Lobster is an **optional** plugin tool (not enabled by default). Allow it per ag
|
|||||||
{
|
{
|
||||||
"id": "main",
|
"id": "main",
|
||||||
"tools": {
|
"tools": {
|
||||||
"allow": ["lobster"]
|
"alsoAllow": ["lobster"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -175,7 +187,7 @@ Lobster is an **optional** plugin tool (not enabled by default). Allow it per ag
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also allow it globally with `tools.allow` if every agent should see it.
|
Avoid using `tools.allow: ["lobster"]` unless you intend to run in restrictive allowlist mode.
|
||||||
|
|
||||||
Note: allowlists are opt-in for optional plugins. If your allowlist only names
|
Note: allowlists are opt-in for optional plugins. If your allowlist only names
|
||||||
plugin tools (like `lobster`), Clawdbot keeps core tools enabled. To restrict core
|
plugin tools (like `lobster`), Clawdbot keeps core tools enabled. To restrict core
|
||||||
|
|||||||
@ -96,13 +96,28 @@ export function filterToolsByPolicy(tools: AnyAgentTool[], policy?: SandboxToolP
|
|||||||
|
|
||||||
type ToolPolicyConfig = {
|
type ToolPolicyConfig = {
|
||||||
allow?: string[];
|
allow?: string[];
|
||||||
|
alsoAllow?: string[];
|
||||||
deny?: string[];
|
deny?: string[];
|
||||||
profile?: string;
|
profile?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function unionAllow(base?: string[], extra?: string[]) {
|
||||||
|
if (!Array.isArray(extra) || extra.length === 0) return base;
|
||||||
|
// If the user is using alsoAllow without an allowlist, treat it as additive on top of
|
||||||
|
// an implicit allow-all policy.
|
||||||
|
if (!Array.isArray(base) || base.length === 0) {
|
||||||
|
return Array.from(new Set(["*", ...extra]));
|
||||||
|
}
|
||||||
|
return Array.from(new Set([...base, ...extra]));
|
||||||
|
}
|
||||||
|
|
||||||
function pickToolPolicy(config?: ToolPolicyConfig): SandboxToolPolicy | undefined {
|
function pickToolPolicy(config?: ToolPolicyConfig): SandboxToolPolicy | undefined {
|
||||||
if (!config) return undefined;
|
if (!config) return undefined;
|
||||||
const allow = Array.isArray(config.allow) ? config.allow : undefined;
|
const allow = Array.isArray(config.allow)
|
||||||
|
? unionAllow(config.allow, config.alsoAllow)
|
||||||
|
: Array.isArray(config.alsoAllow) && config.alsoAllow.length > 0
|
||||||
|
? unionAllow(undefined, config.alsoAllow)
|
||||||
|
: undefined;
|
||||||
const deny = Array.isArray(config.deny) ? config.deny : undefined;
|
const deny = Array.isArray(config.deny) ? config.deny : undefined;
|
||||||
if (!allow && !deny) return undefined;
|
if (!allow && !deny) return undefined;
|
||||||
return { allow, deny };
|
return { allow, deny };
|
||||||
@ -195,6 +210,17 @@ export function resolveEffectiveToolPolicy(params: {
|
|||||||
agentProviderPolicy: pickToolPolicy(agentProviderPolicy),
|
agentProviderPolicy: pickToolPolicy(agentProviderPolicy),
|
||||||
profile,
|
profile,
|
||||||
providerProfile: agentProviderPolicy?.profile ?? providerPolicy?.profile,
|
providerProfile: agentProviderPolicy?.profile ?? providerPolicy?.profile,
|
||||||
|
// alsoAllow is applied at the profile stage (to avoid being filtered out early).
|
||||||
|
profileAlsoAllow: Array.isArray(agentTools?.alsoAllow)
|
||||||
|
? agentTools?.alsoAllow
|
||||||
|
: Array.isArray(globalTools?.alsoAllow)
|
||||||
|
? globalTools?.alsoAllow
|
||||||
|
: undefined,
|
||||||
|
providerProfileAlsoAllow: Array.isArray(agentProviderPolicy?.alsoAllow)
|
||||||
|
? agentProviderPolicy?.alsoAllow
|
||||||
|
: Array.isArray(providerPolicy?.alsoAllow)
|
||||||
|
? providerPolicy?.alsoAllow
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -157,6 +157,8 @@ export function createClawdbotCodingTools(options?: {
|
|||||||
agentProviderPolicy,
|
agentProviderPolicy,
|
||||||
profile,
|
profile,
|
||||||
providerProfile,
|
providerProfile,
|
||||||
|
profileAlsoAllow,
|
||||||
|
providerProfileAlsoAllow,
|
||||||
} = resolveEffectiveToolPolicy({
|
} = resolveEffectiveToolPolicy({
|
||||||
config: options?.config,
|
config: options?.config,
|
||||||
sessionKey: options?.sessionKey,
|
sessionKey: options?.sessionKey,
|
||||||
@ -175,14 +177,25 @@ export function createClawdbotCodingTools(options?: {
|
|||||||
});
|
});
|
||||||
const profilePolicy = resolveToolProfilePolicy(profile);
|
const profilePolicy = resolveToolProfilePolicy(profile);
|
||||||
const providerProfilePolicy = resolveToolProfilePolicy(providerProfile);
|
const providerProfilePolicy = resolveToolProfilePolicy(providerProfile);
|
||||||
|
|
||||||
|
const mergeAlsoAllow = (policy: typeof profilePolicy, alsoAllow?: string[]) => {
|
||||||
|
if (!policy?.allow || !Array.isArray(alsoAllow) || alsoAllow.length === 0) return policy;
|
||||||
|
return { ...policy, allow: Array.from(new Set([...policy.allow, ...alsoAllow])) };
|
||||||
|
};
|
||||||
|
|
||||||
|
const profilePolicyWithAlsoAllow = mergeAlsoAllow(profilePolicy, profileAlsoAllow);
|
||||||
|
const providerProfilePolicyWithAlsoAllow = mergeAlsoAllow(
|
||||||
|
providerProfilePolicy,
|
||||||
|
providerProfileAlsoAllow,
|
||||||
|
);
|
||||||
const scopeKey = options?.exec?.scopeKey ?? (agentId ? `agent:${agentId}` : undefined);
|
const scopeKey = options?.exec?.scopeKey ?? (agentId ? `agent:${agentId}` : undefined);
|
||||||
const subagentPolicy =
|
const subagentPolicy =
|
||||||
isSubagentSessionKey(options?.sessionKey) && options?.sessionKey
|
isSubagentSessionKey(options?.sessionKey) && options?.sessionKey
|
||||||
? resolveSubagentToolPolicy(options.config)
|
? resolveSubagentToolPolicy(options.config)
|
||||||
: undefined;
|
: undefined;
|
||||||
const allowBackground = isToolAllowedByPolicies("process", [
|
const allowBackground = isToolAllowedByPolicies("process", [
|
||||||
profilePolicy,
|
profilePolicyWithAlsoAllow,
|
||||||
providerProfilePolicy,
|
providerProfilePolicyWithAlsoAllow,
|
||||||
globalPolicy,
|
globalPolicy,
|
||||||
globalProviderPolicy,
|
globalProviderPolicy,
|
||||||
agentPolicy,
|
agentPolicy,
|
||||||
@ -333,18 +346,18 @@ export function createClawdbotCodingTools(options?: {
|
|||||||
if (resolved.unknownAllowlist.length > 0) {
|
if (resolved.unknownAllowlist.length > 0) {
|
||||||
const entries = resolved.unknownAllowlist.join(", ");
|
const entries = resolved.unknownAllowlist.join(", ");
|
||||||
const suffix = resolved.strippedAllowlist
|
const suffix = resolved.strippedAllowlist
|
||||||
? "Ignoring allowlist so core tools remain available."
|
? "Ignoring allowlist so core tools remain available. Use tools.alsoAllow for additive plugin tool enablement."
|
||||||
: "These entries won't match any tool unless the plugin is enabled.";
|
: "These entries won't match any tool unless the plugin is enabled.";
|
||||||
logWarn(`tools: ${label} allowlist contains unknown entries (${entries}). ${suffix}`);
|
logWarn(`tools: ${label} allowlist contains unknown entries (${entries}). ${suffix}`);
|
||||||
}
|
}
|
||||||
return expandPolicyWithPluginGroups(resolved.policy, pluginGroups);
|
return expandPolicyWithPluginGroups(resolved.policy, pluginGroups);
|
||||||
};
|
};
|
||||||
const profilePolicyExpanded = resolvePolicy(
|
const profilePolicyExpanded = resolvePolicy(
|
||||||
profilePolicy,
|
profilePolicyWithAlsoAllow,
|
||||||
profile ? `tools.profile (${profile})` : "tools.profile",
|
profile ? `tools.profile (${profile})` : "tools.profile",
|
||||||
);
|
);
|
||||||
const providerProfileExpanded = resolvePolicy(
|
const providerProfileExpanded = resolvePolicy(
|
||||||
providerProfilePolicy,
|
providerProfilePolicyWithAlsoAllow,
|
||||||
providerProfile ? `tools.byProvider.profile (${providerProfile})` : "tools.byProvider.profile",
|
providerProfile ? `tools.byProvider.profile (${providerProfile})` : "tools.byProvider.profile",
|
||||||
);
|
);
|
||||||
const globalPolicyExpanded = resolvePolicy(globalPolicy, "tools.allow");
|
const globalPolicyExpanded = resolvePolicy(globalPolicy, "tools.allow");
|
||||||
|
|||||||
@ -209,6 +209,12 @@ export function stripPluginOnlyAllowlist(
|
|||||||
if (!isCoreEntry && !isPluginEntry) unknownAllowlist.push(entry);
|
if (!isCoreEntry && !isPluginEntry) unknownAllowlist.push(entry);
|
||||||
}
|
}
|
||||||
const strippedAllowlist = !hasCoreEntry;
|
const strippedAllowlist = !hasCoreEntry;
|
||||||
|
// When an allowlist contains only plugin tools, we strip it to avoid accidentally
|
||||||
|
// disabling core tools. Users who want additive behavior should prefer `tools.alsoAllow`.
|
||||||
|
if (strippedAllowlist) {
|
||||||
|
// Note: logging happens in the caller (pi-tools/tools-invoke) after this function returns.
|
||||||
|
// We keep this note here for future maintainers.
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
policy: strippedAllowlist ? { ...policy, allow: undefined } : policy,
|
policy: strippedAllowlist ? { ...policy, allow: undefined } : policy,
|
||||||
unknownAllowlist: Array.from(new Set(unknownAllowlist)),
|
unknownAllowlist: Array.from(new Set(unknownAllowlist)),
|
||||||
|
|||||||
53
src/config/config.tools-alsoAllow.test.ts
Normal file
53
src/config/config.tools-alsoAllow.test.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { validateConfigObject } from "./validation.js";
|
||||||
|
|
||||||
|
// NOTE: These tests ensure allow + alsoAllow cannot be set in the same scope.
|
||||||
|
|
||||||
|
describe("config: tools.alsoAllow", () => {
|
||||||
|
it("rejects tools.allow + tools.alsoAllow together", () => {
|
||||||
|
const res = validateConfigObject({
|
||||||
|
tools: {
|
||||||
|
allow: ["group:fs"],
|
||||||
|
alsoAllow: ["lobster"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.ok).toBe(false);
|
||||||
|
if (!res.ok) {
|
||||||
|
expect(res.issues.some((i) => i.path === "tools")).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects agents.list[].tools.allow + alsoAllow together", () => {
|
||||||
|
const res = validateConfigObject({
|
||||||
|
agents: {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
id: "main",
|
||||||
|
tools: {
|
||||||
|
allow: ["group:fs"],
|
||||||
|
alsoAllow: ["lobster"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.ok).toBe(false);
|
||||||
|
if (!res.ok) {
|
||||||
|
expect(res.issues.some((i) => i.path.includes("agents.list"))).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows profile + alsoAllow", () => {
|
||||||
|
const res = validateConfigObject({
|
||||||
|
tools: {
|
||||||
|
profile: "coding",
|
||||||
|
alsoAllow: ["lobster"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.ok).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -9,6 +9,10 @@ import {
|
|||||||
resolveDefaultAgentIdFromRaw,
|
resolveDefaultAgentIdFromRaw,
|
||||||
} from "./legacy.shared.js";
|
} from "./legacy.shared.js";
|
||||||
|
|
||||||
|
// NOTE: tools.alsoAllow was introduced after legacy migrations; no legacy migration needed.
|
||||||
|
|
||||||
|
// tools.alsoAllow legacy migration intentionally omitted (field not shipped in prod).
|
||||||
|
|
||||||
export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [
|
export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [
|
||||||
{
|
{
|
||||||
id: "auth.anthropic-claude-cli-mode-oauth",
|
id: "auth.anthropic-claude-cli-mode-oauth",
|
||||||
@ -24,6 +28,7 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [
|
|||||||
changes.push('Updated auth.profiles["anthropic:claude-cli"].mode → "oauth".');
|
changes.push('Updated auth.profiles["anthropic:claude-cli"].mode → "oauth".');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// tools.alsoAllow migration removed (field not shipped in prod; enforce via schema instead).
|
||||||
{
|
{
|
||||||
id: "tools.bash->tools.exec",
|
id: "tools.bash->tools.exec",
|
||||||
describe: "Move tools.bash to tools.exec",
|
describe: "Move tools.bash to tools.exec",
|
||||||
|
|||||||
@ -165,7 +165,9 @@ const FIELD_LABELS: Record<string, string> = {
|
|||||||
"tools.links.models": "Link Understanding Models",
|
"tools.links.models": "Link Understanding Models",
|
||||||
"tools.links.scope": "Link Understanding Scope",
|
"tools.links.scope": "Link Understanding Scope",
|
||||||
"tools.profile": "Tool Profile",
|
"tools.profile": "Tool Profile",
|
||||||
|
"tools.alsoAllow": "Tool Allowlist Additions",
|
||||||
"agents.list[].tools.profile": "Agent Tool Profile",
|
"agents.list[].tools.profile": "Agent Tool Profile",
|
||||||
|
"agents.list[].tools.alsoAllow": "Agent Tool Allowlist Additions",
|
||||||
"tools.byProvider": "Tool Policy by Provider",
|
"tools.byProvider": "Tool Policy by Provider",
|
||||||
"agents.list[].tools.byProvider": "Agent Tool Policy by Provider",
|
"agents.list[].tools.byProvider": "Agent Tool Policy by Provider",
|
||||||
"tools.exec.applyPatch.enabled": "Enable apply_patch",
|
"tools.exec.applyPatch.enabled": "Enable apply_patch",
|
||||||
|
|||||||
@ -140,12 +140,21 @@ export type ToolProfileId = "minimal" | "coding" | "messaging" | "full";
|
|||||||
|
|
||||||
export type ToolPolicyConfig = {
|
export type ToolPolicyConfig = {
|
||||||
allow?: string[];
|
allow?: string[];
|
||||||
|
/**
|
||||||
|
* Additional allowlist entries merged into the effective allowlist.
|
||||||
|
*
|
||||||
|
* Intended for additive configuration (e.g., "also allow lobster") without forcing
|
||||||
|
* users to replace/duplicate an existing allowlist or profile.
|
||||||
|
*/
|
||||||
|
alsoAllow?: string[];
|
||||||
deny?: string[];
|
deny?: string[];
|
||||||
profile?: ToolProfileId;
|
profile?: ToolProfileId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GroupToolPolicyConfig = {
|
export type GroupToolPolicyConfig = {
|
||||||
allow?: string[];
|
allow?: string[];
|
||||||
|
/** Additional allowlist entries merged into allow. */
|
||||||
|
alsoAllow?: string[];
|
||||||
deny?: string[];
|
deny?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -188,6 +197,8 @@ export type AgentToolsConfig = {
|
|||||||
/** Base tool profile applied before allow/deny lists. */
|
/** Base tool profile applied before allow/deny lists. */
|
||||||
profile?: ToolProfileId;
|
profile?: ToolProfileId;
|
||||||
allow?: string[];
|
allow?: string[];
|
||||||
|
/** Additional allowlist entries merged into allow and/or profile allowlist. */
|
||||||
|
alsoAllow?: string[];
|
||||||
deny?: string[];
|
deny?: string[];
|
||||||
/** Optional tool policy overrides keyed by provider id or "provider/model". */
|
/** Optional tool policy overrides keyed by provider id or "provider/model". */
|
||||||
byProvider?: Record<string, ToolPolicyConfig>;
|
byProvider?: Record<string, ToolPolicyConfig>;
|
||||||
@ -312,6 +323,8 @@ export type ToolsConfig = {
|
|||||||
/** Base tool profile applied before allow/deny lists. */
|
/** Base tool profile applied before allow/deny lists. */
|
||||||
profile?: ToolProfileId;
|
profile?: ToolProfileId;
|
||||||
allow?: string[];
|
allow?: string[];
|
||||||
|
/** Additional allowlist entries merged into allow and/or profile allowlist. */
|
||||||
|
alsoAllow?: string[];
|
||||||
deny?: string[];
|
deny?: string[];
|
||||||
/** Optional tool policy overrides keyed by provider id or "provider/model". */
|
/** Optional tool policy overrides keyed by provider id or "provider/model". */
|
||||||
byProvider?: Record<string, ToolPolicyConfig>;
|
byProvider?: Record<string, ToolPolicyConfig>;
|
||||||
|
|||||||
@ -147,13 +147,22 @@ export const SandboxPruneSchema = z
|
|||||||
.strict()
|
.strict()
|
||||||
.optional();
|
.optional();
|
||||||
|
|
||||||
export const ToolPolicySchema = z
|
const ToolPolicyBaseSchema = z
|
||||||
.object({
|
.object({
|
||||||
allow: z.array(z.string()).optional(),
|
allow: z.array(z.string()).optional(),
|
||||||
|
alsoAllow: z.array(z.string()).optional(),
|
||||||
deny: z.array(z.string()).optional(),
|
deny: z.array(z.string()).optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict();
|
||||||
.optional();
|
|
||||||
|
export const ToolPolicySchema = ToolPolicyBaseSchema.superRefine((value, ctx) => {
|
||||||
|
if (value.allow && value.allow.length > 0 && value.alsoAllow && value.alsoAllow.length > 0) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: "tools policy cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).optional();
|
||||||
|
|
||||||
export const ToolsWebSearchSchema = z
|
export const ToolsWebSearchSchema = z
|
||||||
.object({
|
.object({
|
||||||
@ -202,10 +211,20 @@ export const ToolProfileSchema = z
|
|||||||
export const ToolPolicyWithProfileSchema = z
|
export const ToolPolicyWithProfileSchema = z
|
||||||
.object({
|
.object({
|
||||||
allow: z.array(z.string()).optional(),
|
allow: z.array(z.string()).optional(),
|
||||||
|
alsoAllow: z.array(z.string()).optional(),
|
||||||
deny: z.array(z.string()).optional(),
|
deny: z.array(z.string()).optional(),
|
||||||
profile: ToolProfileSchema,
|
profile: ToolProfileSchema,
|
||||||
})
|
})
|
||||||
.strict();
|
.strict()
|
||||||
|
.superRefine((value, ctx) => {
|
||||||
|
if (value.allow && value.allow.length > 0 && value.alsoAllow && value.alsoAllow.length > 0) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message:
|
||||||
|
"tools.byProvider policy cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Provider docking: allowlists keyed by provider id (no schema updates when adding providers).
|
// Provider docking: allowlists keyed by provider id (no schema updates when adding providers).
|
||||||
export const ElevatedAllowFromSchema = z
|
export const ElevatedAllowFromSchema = z
|
||||||
@ -231,6 +250,7 @@ export const AgentToolsSchema = z
|
|||||||
.object({
|
.object({
|
||||||
profile: ToolProfileSchema,
|
profile: ToolProfileSchema,
|
||||||
allow: z.array(z.string()).optional(),
|
allow: z.array(z.string()).optional(),
|
||||||
|
alsoAllow: z.array(z.string()).optional(),
|
||||||
deny: z.array(z.string()).optional(),
|
deny: z.array(z.string()).optional(),
|
||||||
byProvider: z.record(z.string(), ToolPolicyWithProfileSchema).optional(),
|
byProvider: z.record(z.string(), ToolPolicyWithProfileSchema).optional(),
|
||||||
elevated: z
|
elevated: z
|
||||||
@ -271,6 +291,15 @@ export const AgentToolsSchema = z
|
|||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
.superRefine((value, ctx) => {
|
||||||
|
if (value.allow && value.allow.length > 0 && value.alsoAllow && value.alsoAllow.length > 0) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message:
|
||||||
|
"agent tools cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
.optional();
|
.optional();
|
||||||
|
|
||||||
export const MemorySearchSchema = z
|
export const MemorySearchSchema = z
|
||||||
@ -425,6 +454,7 @@ export const ToolsSchema = z
|
|||||||
.object({
|
.object({
|
||||||
profile: ToolProfileSchema,
|
profile: ToolProfileSchema,
|
||||||
allow: z.array(z.string()).optional(),
|
allow: z.array(z.string()).optional(),
|
||||||
|
alsoAllow: z.array(z.string()).optional(),
|
||||||
deny: z.array(z.string()).optional(),
|
deny: z.array(z.string()).optional(),
|
||||||
byProvider: z.record(z.string(), ToolPolicyWithProfileSchema).optional(),
|
byProvider: z.record(z.string(), ToolPolicyWithProfileSchema).optional(),
|
||||||
web: ToolsWebSchema,
|
web: ToolsWebSchema,
|
||||||
@ -507,4 +537,13 @@ export const ToolsSchema = z
|
|||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
.superRefine((value, ctx) => {
|
||||||
|
if (value.allow && value.allow.length > 0 && value.alsoAllow && value.alsoAllow.length > 0) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message:
|
||||||
|
"tools cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
.optional();
|
.optional();
|
||||||
|
|||||||
@ -1,12 +1,19 @@
|
|||||||
import { describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||||
|
|
||||||
import { installGatewayTestHooks, getFreePort, startGatewayServer } from "./test-helpers.server.js";
|
import { installGatewayTestHooks, getFreePort, startGatewayServer } from "./test-helpers.server.js";
|
||||||
import { resetTestPluginRegistry, setTestPluginRegistry, testState } from "./test-helpers.mocks.js";
|
import { resetTestPluginRegistry, setTestPluginRegistry, testState } from "./test-helpers.mocks.js";
|
||||||
import { createTestRegistry } from "../test-utils/channel-plugins.js";
|
import { createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||||
|
|
||||||
installGatewayTestHooks({ scope: "suite" });
|
installGatewayTestHooks({ scope: "suite" });
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Ensure these tests are not affected by host env vars.
|
||||||
|
delete process.env.CLAWDBOT_GATEWAY_TOKEN;
|
||||||
|
delete process.env.CLAWDBOT_GATEWAY_PASSWORD;
|
||||||
|
});
|
||||||
|
|
||||||
const resolveGatewayToken = (): string => {
|
const resolveGatewayToken = (): string => {
|
||||||
const token = (testState.gatewayAuth as { token?: string } | undefined)?.token;
|
const token = (testState.gatewayAuth as { token?: string } | undefined)?.token;
|
||||||
if (!token) throw new Error("test gateway token missing");
|
if (!token) throw new Error("test gateway token missing");
|
||||||
@ -47,6 +54,63 @@ describe("POST /tools/invoke", () => {
|
|||||||
await server.close();
|
await server.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("supports tools.alsoAllow as additive allowlist (profile stage)", async () => {
|
||||||
|
// No explicit tool allowlist; rely on profile + alsoAllow.
|
||||||
|
testState.agentsConfig = {
|
||||||
|
list: [{ id: "main" }],
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// minimal profile does NOT include sessions_list, but alsoAllow should.
|
||||||
|
const { writeConfigFile } = await import("../config/config.js");
|
||||||
|
await writeConfigFile({
|
||||||
|
tools: { profile: "minimal", alsoAllow: ["sessions_list"] },
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
const port = await getFreePort();
|
||||||
|
const server = await startGatewayServer(port, { bind: "loopback" });
|
||||||
|
const token = resolveGatewayToken();
|
||||||
|
|
||||||
|
const res = await fetch(`http://127.0.0.1:${port}/tools/invoke`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
||||||
|
body: JSON.stringify({ tool: "sessions_list", action: "json", args: {}, sessionKey: "main" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
const body = await res.json();
|
||||||
|
expect(body.ok).toBe(true);
|
||||||
|
|
||||||
|
await server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports tools.alsoAllow without allow/profile (implicit allow-all)", async () => {
|
||||||
|
testState.agentsConfig = {
|
||||||
|
list: [{ id: "main" }],
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await fs.mkdir(path.dirname(CONFIG_PATH_CLAWDBOT), { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
CONFIG_PATH_CLAWDBOT,
|
||||||
|
JSON.stringify({ tools: { alsoAllow: ["sessions_list"] } }, null, 2),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
|
||||||
|
const port = await getFreePort();
|
||||||
|
const server = await startGatewayServer(port, { bind: "loopback" });
|
||||||
|
|
||||||
|
const res = await fetch(`http://127.0.0.1:${port}/tools/invoke`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ tool: "sessions_list", action: "json", args: {}, sessionKey: "main" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
const body = await res.json();
|
||||||
|
expect(body.ok).toBe(true);
|
||||||
|
|
||||||
|
await server.close();
|
||||||
|
});
|
||||||
|
|
||||||
it("accepts password auth when bearer token matches", async () => {
|
it("accepts password auth when bearer token matches", async () => {
|
||||||
testState.agentsConfig = {
|
testState.agentsConfig = {
|
||||||
list: [
|
list: [
|
||||||
|
|||||||
@ -130,9 +130,22 @@ export async function handleToolsInvokeHttpRequest(
|
|||||||
agentProviderPolicy,
|
agentProviderPolicy,
|
||||||
profile,
|
profile,
|
||||||
providerProfile,
|
providerProfile,
|
||||||
|
profileAlsoAllow,
|
||||||
|
providerProfileAlsoAllow,
|
||||||
} = resolveEffectiveToolPolicy({ config: cfg, sessionKey });
|
} = resolveEffectiveToolPolicy({ config: cfg, sessionKey });
|
||||||
const profilePolicy = resolveToolProfilePolicy(profile);
|
const profilePolicy = resolveToolProfilePolicy(profile);
|
||||||
const providerProfilePolicy = resolveToolProfilePolicy(providerProfile);
|
const providerProfilePolicy = resolveToolProfilePolicy(providerProfile);
|
||||||
|
|
||||||
|
const mergeAlsoAllow = (policy: typeof profilePolicy, alsoAllow?: string[]) => {
|
||||||
|
if (!policy?.allow || !Array.isArray(alsoAllow) || alsoAllow.length === 0) return policy;
|
||||||
|
return { ...policy, allow: Array.from(new Set([...policy.allow, ...alsoAllow])) };
|
||||||
|
};
|
||||||
|
|
||||||
|
const profilePolicyWithAlsoAllow = mergeAlsoAllow(profilePolicy, profileAlsoAllow);
|
||||||
|
const providerProfilePolicyWithAlsoAllow = mergeAlsoAllow(
|
||||||
|
providerProfilePolicy,
|
||||||
|
providerProfileAlsoAllow,
|
||||||
|
);
|
||||||
const groupPolicy = resolveGroupToolPolicy({
|
const groupPolicy = resolveGroupToolPolicy({
|
||||||
config: cfg,
|
config: cfg,
|
||||||
sessionKey,
|
sessionKey,
|
||||||
@ -176,18 +189,18 @@ export async function handleToolsInvokeHttpRequest(
|
|||||||
if (resolved.unknownAllowlist.length > 0) {
|
if (resolved.unknownAllowlist.length > 0) {
|
||||||
const entries = resolved.unknownAllowlist.join(", ");
|
const entries = resolved.unknownAllowlist.join(", ");
|
||||||
const suffix = resolved.strippedAllowlist
|
const suffix = resolved.strippedAllowlist
|
||||||
? "Ignoring allowlist so core tools remain available."
|
? "Ignoring allowlist so core tools remain available. Use tools.alsoAllow for additive plugin tool enablement."
|
||||||
: "These entries won't match any tool unless the plugin is enabled.";
|
: "These entries won't match any tool unless the plugin is enabled.";
|
||||||
logWarn(`tools: ${label} allowlist contains unknown entries (${entries}). ${suffix}`);
|
logWarn(`tools: ${label} allowlist contains unknown entries (${entries}). ${suffix}`);
|
||||||
}
|
}
|
||||||
return expandPolicyWithPluginGroups(resolved.policy, pluginGroups);
|
return expandPolicyWithPluginGroups(resolved.policy, pluginGroups);
|
||||||
};
|
};
|
||||||
const profilePolicyExpanded = resolvePolicy(
|
const profilePolicyExpanded = resolvePolicy(
|
||||||
profilePolicy,
|
profilePolicyWithAlsoAllow,
|
||||||
profile ? `tools.profile (${profile})` : "tools.profile",
|
profile ? `tools.profile (${profile})` : "tools.profile",
|
||||||
);
|
);
|
||||||
const providerProfileExpanded = resolvePolicy(
|
const providerProfileExpanded = resolvePolicy(
|
||||||
providerProfilePolicy,
|
providerProfilePolicyWithAlsoAllow,
|
||||||
providerProfile ? `tools.byProvider.profile (${providerProfile})` : "tools.byProvider.profile",
|
providerProfile ? `tools.byProvider.profile (${providerProfile})` : "tools.byProvider.profile",
|
||||||
);
|
);
|
||||||
const globalPolicyExpanded = resolvePolicy(globalPolicy, "tools.allow");
|
const globalPolicyExpanded = resolvePolicy(globalPolicy, "tools.allow");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user