mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
feat(policy): add --admin-policy flag for supplemental admin policies (#20360)
This commit is contained in:
@@ -92,6 +92,13 @@ their corresponding top-level category object in your `settings.json` file.
|
|||||||
- **Default:** `[]`
|
- **Default:** `[]`
|
||||||
- **Requires restart:** Yes
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
|
#### `adminPolicyPaths`
|
||||||
|
|
||||||
|
- **`adminPolicyPaths`** (array):
|
||||||
|
- **Description:** Additional admin policy files or directories to load.
|
||||||
|
- **Default:** `[]`
|
||||||
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
#### `general`
|
#### `general`
|
||||||
|
|
||||||
- **`general.preferredEditor`** (string):
|
- **`general.preferredEditor`** (string):
|
||||||
|
|||||||
@@ -191,9 +191,13 @@ User, and (if configured) Admin directories.
|
|||||||
|
|
||||||
#### System-wide policies (Admin)
|
#### System-wide policies (Admin)
|
||||||
|
|
||||||
Administrators can enforce system-wide policies (Tier 3) that override all user
|
Administrators can enforce system-wide policies (Tier 4) that override all user
|
||||||
and default settings. These policies must be placed in specific, secure
|
and default settings. These policies can be loaded from standard system
|
||||||
directories:
|
locations or supplemental paths.
|
||||||
|
|
||||||
|
##### Standard Locations
|
||||||
|
|
||||||
|
These are the default paths the CLI searches for admin policies:
|
||||||
|
|
||||||
| OS | Policy Directory Path |
|
| OS | Policy Directory Path |
|
||||||
| :---------- | :------------------------------------------------ |
|
| :---------- | :------------------------------------------------ |
|
||||||
@@ -201,10 +205,25 @@ directories:
|
|||||||
| **macOS** | `/Library/Application Support/GeminiCli/policies` |
|
| **macOS** | `/Library/Application Support/GeminiCli/policies` |
|
||||||
| **Windows** | `C:\ProgramData\gemini-cli\policies` |
|
| **Windows** | `C:\ProgramData\gemini-cli\policies` |
|
||||||
|
|
||||||
**Security Requirements:**
|
##### Supplemental Admin Policies
|
||||||
|
|
||||||
To prevent privilege escalation, the CLI enforces strict security checks on
|
Administrators can also specify supplemental policy paths using:
|
||||||
admin directories. If checks fail, system policies are **ignored**.
|
|
||||||
|
- The `--admin-policy` command-line flag.
|
||||||
|
- The `adminPolicyPaths` setting in a system settings file.
|
||||||
|
|
||||||
|
These supplemental policies are assigned the same **Admin** tier (Base 4) as
|
||||||
|
policies in standard locations.
|
||||||
|
|
||||||
|
**Security Guard**: Supplemental admin policies are **ignored** if any `.toml`
|
||||||
|
policy files are found in the standard system location. This prevents flag-based
|
||||||
|
overrides when a central system policy has already been established.
|
||||||
|
|
||||||
|
#### Security Requirements
|
||||||
|
|
||||||
|
To prevent privilege escalation, the CLI enforces strict security checks on the
|
||||||
|
**standard system policy directory**. If checks fail, the policies in that
|
||||||
|
directory are **ignored**.
|
||||||
|
|
||||||
- **Linux / macOS:** Must be owned by `root` (UID 0) and NOT writable by group
|
- **Linux / macOS:** Must be owned by `root` (UID 0) and NOT writable by group
|
||||||
or others (e.g., `chmod 755`).
|
or others (e.g., `chmod 755`).
|
||||||
@@ -214,6 +233,11 @@ admin directories. If checks fail, system policies are **ignored**.
|
|||||||
for non-admin groups. You may need to "Disable inheritance" in Advanced
|
for non-admin groups. You may need to "Disable inheritance" in Advanced
|
||||||
Security Settings._
|
Security Settings._
|
||||||
|
|
||||||
|
**Note:** Supplemental admin policies (provided via `--admin-policy` or
|
||||||
|
`adminPolicyPaths` settings) are **NOT** subject to these strict ownership
|
||||||
|
checks, as they are explicitly provided by the user or administrator in their
|
||||||
|
current execution context.
|
||||||
|
|
||||||
### TOML rule schema
|
### TOML rule schema
|
||||||
|
|
||||||
Here is a breakdown of the fields available in a TOML policy rule:
|
Here is a breakdown of the fields available in a TOML policy rule:
|
||||||
|
|||||||
Generated
+25
-1
@@ -2195,6 +2195,7 @@
|
|||||||
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
|
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-token": "^6.0.0",
|
"@octokit/auth-token": "^6.0.0",
|
||||||
"@octokit/graphql": "^9.0.2",
|
"@octokit/graphql": "^9.0.2",
|
||||||
@@ -2375,6 +2376,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
}
|
}
|
||||||
@@ -2424,6 +2426,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz",
|
||||||
"integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==",
|
"integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||||
},
|
},
|
||||||
@@ -2798,6 +2801,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz",
|
||||||
"integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==",
|
"integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/core": "2.5.0",
|
"@opentelemetry/core": "2.5.0",
|
||||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||||
@@ -2831,6 +2835,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz",
|
||||||
"integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==",
|
"integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/core": "2.5.0",
|
"@opentelemetry/core": "2.5.0",
|
||||||
"@opentelemetry/resources": "2.5.0"
|
"@opentelemetry/resources": "2.5.0"
|
||||||
@@ -2885,6 +2890,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz",
|
||||||
"integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==",
|
"integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/core": "2.5.0",
|
"@opentelemetry/core": "2.5.0",
|
||||||
"@opentelemetry/resources": "2.5.0",
|
"@opentelemetry/resources": "2.5.0",
|
||||||
@@ -4048,6 +4054,7 @@
|
|||||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -4322,6 +4329,7 @@
|
|||||||
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.35.0",
|
"@typescript-eslint/scope-manager": "8.35.0",
|
||||||
"@typescript-eslint/types": "8.35.0",
|
"@typescript-eslint/types": "8.35.0",
|
||||||
@@ -5195,6 +5203,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -7726,6 +7735,7 @@
|
|||||||
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -8236,6 +8246,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "^2.0.0",
|
"accepts": "^2.0.0",
|
||||||
"body-parser": "^2.2.1",
|
"body-parser": "^2.2.1",
|
||||||
@@ -9503,6 +9514,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz",
|
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz",
|
||||||
"integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==",
|
"integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.9.0"
|
"node": ">=16.9.0"
|
||||||
}
|
}
|
||||||
@@ -9782,6 +9794,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.11.tgz",
|
||||||
"integrity": "sha512-93LQlzT7vvZ1XJcmOMwN4s+6W334QegendeHOMnEJBlhnpIzr8bws6/aOEHG8ZCuVD/vNeeea5m1msHIdAY6ig==",
|
"integrity": "sha512-93LQlzT7vvZ1XJcmOMwN4s+6W334QegendeHOMnEJBlhnpIzr8bws6/aOEHG8ZCuVD/vNeeea5m1msHIdAY6ig==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alcalzone/ansi-tokenize": "^0.2.1",
|
"@alcalzone/ansi-tokenize": "^0.2.1",
|
||||||
"ansi-escapes": "^7.0.0",
|
"ansi-escapes": "^7.0.0",
|
||||||
@@ -13368,6 +13381,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -13378,6 +13392,7 @@
|
|||||||
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
|
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"shell-quote": "^1.6.1",
|
"shell-quote": "^1.6.1",
|
||||||
"ws": "^7"
|
"ws": "^7"
|
||||||
@@ -15422,6 +15437,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -15645,7 +15661,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "0BSD"
|
"license": "0BSD",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/tsx": {
|
"node_modules/tsx": {
|
||||||
"version": "4.20.3",
|
"version": "4.20.3",
|
||||||
@@ -15653,6 +15670,7 @@
|
|||||||
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "~0.25.0",
|
"esbuild": "~0.25.0",
|
||||||
"get-tsconfig": "^4.7.5"
|
"get-tsconfig": "^4.7.5"
|
||||||
@@ -15812,6 +15830,7 @@
|
|||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -16034,6 +16053,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
||||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -16147,6 +16167,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -16159,6 +16180,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
|
||||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/chai": "^5.2.2",
|
"@types/chai": "^5.2.2",
|
||||||
"@vitest/expect": "3.2.4",
|
"@vitest/expect": "3.2.4",
|
||||||
@@ -16800,6 +16822,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
@@ -17336,6 +17359,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export interface CliArgs {
|
|||||||
yolo: boolean | undefined;
|
yolo: boolean | undefined;
|
||||||
approvalMode: string | undefined;
|
approvalMode: string | undefined;
|
||||||
policy: string[] | undefined;
|
policy: string[] | undefined;
|
||||||
|
adminPolicy: string[] | undefined;
|
||||||
allowedMcpServerNames: string[] | undefined;
|
allowedMcpServerNames: string[] | undefined;
|
||||||
allowedTools: string[] | undefined;
|
allowedTools: string[] | undefined;
|
||||||
acp?: boolean;
|
acp?: boolean;
|
||||||
@@ -97,6 +98,21 @@ export interface CliArgs {
|
|||||||
isCommand: boolean | undefined;
|
isCommand: boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to coerce comma-separated or multiple flag values into a flat array.
|
||||||
|
*/
|
||||||
|
const coerceCommaSeparated = (values: string[]): string[] => {
|
||||||
|
if (values.length === 1 && values[0] === '') {
|
||||||
|
return [''];
|
||||||
|
}
|
||||||
|
return values.flatMap((v) =>
|
||||||
|
v
|
||||||
|
.split(',')
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export async function parseArguments(
|
export async function parseArguments(
|
||||||
settings: MergedSettings,
|
settings: MergedSettings,
|
||||||
): Promise<CliArgs> {
|
): Promise<CliArgs> {
|
||||||
@@ -166,14 +182,15 @@ export async function parseArguments(
|
|||||||
nargs: 1,
|
nargs: 1,
|
||||||
description:
|
description:
|
||||||
'Additional policy files or directories to load (comma-separated or multiple --policy)',
|
'Additional policy files or directories to load (comma-separated or multiple --policy)',
|
||||||
coerce: (policies: string[]) =>
|
coerce: coerceCommaSeparated,
|
||||||
// Handle comma-separated values
|
})
|
||||||
policies.flatMap((p) =>
|
.option('admin-policy', {
|
||||||
p
|
type: 'array',
|
||||||
.split(',')
|
string: true,
|
||||||
.map((s) => s.trim())
|
nargs: 1,
|
||||||
.filter(Boolean),
|
description:
|
||||||
),
|
'Additional admin policy files or directories to load (comma-separated or multiple --admin-policy)',
|
||||||
|
coerce: coerceCommaSeparated,
|
||||||
})
|
})
|
||||||
.option('acp', {
|
.option('acp', {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
@@ -189,11 +206,7 @@ export async function parseArguments(
|
|||||||
string: true,
|
string: true,
|
||||||
nargs: 1,
|
nargs: 1,
|
||||||
description: 'Allowed MCP server names',
|
description: 'Allowed MCP server names',
|
||||||
coerce: (mcpServerNames: string[]) =>
|
coerce: coerceCommaSeparated,
|
||||||
// Handle comma-separated values
|
|
||||||
mcpServerNames.flatMap((mcpServerName) =>
|
|
||||||
mcpServerName.split(',').map((m) => m.trim()),
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
.option('allowed-tools', {
|
.option('allowed-tools', {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
@@ -201,9 +214,7 @@ export async function parseArguments(
|
|||||||
nargs: 1,
|
nargs: 1,
|
||||||
description:
|
description:
|
||||||
'[DEPRECATED: Use Policy Engine instead See https://geminicli.com/docs/core/policy-engine] Tools that are allowed to run without confirmation',
|
'[DEPRECATED: Use Policy Engine instead See https://geminicli.com/docs/core/policy-engine] Tools that are allowed to run without confirmation',
|
||||||
coerce: (tools: string[]) =>
|
coerce: coerceCommaSeparated,
|
||||||
// Handle comma-separated values
|
|
||||||
tools.flatMap((tool) => tool.split(',').map((t) => t.trim())),
|
|
||||||
})
|
})
|
||||||
.option('extensions', {
|
.option('extensions', {
|
||||||
alias: 'e',
|
alias: 'e',
|
||||||
@@ -212,11 +223,7 @@ export async function parseArguments(
|
|||||||
nargs: 1,
|
nargs: 1,
|
||||||
description:
|
description:
|
||||||
'A list of extensions to use. If not provided, all extensions are used.',
|
'A list of extensions to use. If not provided, all extensions are used.',
|
||||||
coerce: (extensions: string[]) =>
|
coerce: coerceCommaSeparated,
|
||||||
// Handle comma-separated values
|
|
||||||
extensions.flatMap((extension) =>
|
|
||||||
extension.split(',').map((e) => e.trim()),
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
.option('list-extensions', {
|
.option('list-extensions', {
|
||||||
alias: 'l',
|
alias: 'l',
|
||||||
@@ -258,9 +265,7 @@ export async function parseArguments(
|
|||||||
nargs: 1,
|
nargs: 1,
|
||||||
description:
|
description:
|
||||||
'Additional directories to include in the workspace (comma-separated or multiple --include-directories)',
|
'Additional directories to include in the workspace (comma-separated or multiple --include-directories)',
|
||||||
coerce: (dirs: string[]) =>
|
coerce: coerceCommaSeparated,
|
||||||
// Handle comma-separated values
|
|
||||||
dirs.flatMap((dir) => dir.split(',').map((d) => d.trim())),
|
|
||||||
})
|
})
|
||||||
.option('screen-reader', {
|
.option('screen-reader', {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
@@ -643,7 +648,8 @@ export async function loadCliConfig(
|
|||||||
...settings.mcp,
|
...settings.mcp,
|
||||||
allowed: argv.allowedMcpServerNames ?? settings.mcp?.allowed,
|
allowed: argv.allowedMcpServerNames ?? settings.mcp?.allowed,
|
||||||
},
|
},
|
||||||
policyPaths: argv.policy,
|
policyPaths: argv.policy ?? settings.policyPaths,
|
||||||
|
adminPolicyPaths: argv.adminPolicy ?? settings.adminPolicyPaths,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { workspacePoliciesDir, policyUpdateConfirmationRequest } =
|
const { workspacePoliciesDir, policyUpdateConfirmationRequest } =
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export async function createPolicyEngineConfig(
|
|||||||
tools: settings.tools,
|
tools: settings.tools,
|
||||||
mcpServers: settings.mcpServers,
|
mcpServers: settings.mcpServers,
|
||||||
policyPaths: settings.policyPaths,
|
policyPaths: settings.policyPaths,
|
||||||
|
adminPolicyPaths: settings.adminPolicyPaths,
|
||||||
workspacePoliciesDir,
|
workspacePoliciesDir,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -134,6 +134,18 @@ export interface SettingsSchema {
|
|||||||
export type MemoryImportFormat = 'tree' | 'flat';
|
export type MemoryImportFormat = 'tree' | 'flat';
|
||||||
export type DnsResolutionOrder = 'ipv4first' | 'verbatim';
|
export type DnsResolutionOrder = 'ipv4first' | 'verbatim';
|
||||||
|
|
||||||
|
const pathArraySetting = (label: string, description: string) => ({
|
||||||
|
type: 'array' as const,
|
||||||
|
label,
|
||||||
|
category: 'Advanced' as const,
|
||||||
|
requiresRestart: true as const,
|
||||||
|
default: [] as string[],
|
||||||
|
description,
|
||||||
|
showInDialog: false as const,
|
||||||
|
items: { type: 'string' as const },
|
||||||
|
mergeStrategy: MergeStrategy.UNION,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The canonical schema for all settings.
|
* The canonical schema for all settings.
|
||||||
* The structure of this object defines the structure of the `Settings` type.
|
* The structure of this object defines the structure of the `Settings` type.
|
||||||
@@ -156,17 +168,15 @@ const SETTINGS_SCHEMA = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
policyPaths: {
|
policyPaths: pathArraySetting(
|
||||||
type: 'array',
|
'Policy Paths',
|
||||||
label: 'Policy Paths',
|
'Additional policy files or directories to load.',
|
||||||
category: 'Advanced',
|
),
|
||||||
requiresRestart: true,
|
|
||||||
default: [] as string[],
|
adminPolicyPaths: pathArraySetting(
|
||||||
description: 'Additional policy files or directories to load.',
|
'Admin Policy Paths',
|
||||||
showInDialog: false,
|
'Additional admin policy files or directories to load.',
|
||||||
items: { type: 'string' },
|
),
|
||||||
mergeStrategy: MergeStrategy.UNION,
|
|
||||||
},
|
|
||||||
|
|
||||||
general: {
|
general: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -2677,6 +2687,8 @@ type InferSettings<T extends SettingsSchema> = {
|
|||||||
? boolean
|
? boolean
|
||||||
: T[K]['default'] extends string
|
: T[K]['default'] extends string
|
||||||
? string
|
? string
|
||||||
|
: T[K]['default'] extends ReadonlyArray<infer U>
|
||||||
|
? U[]
|
||||||
: T[K]['default'];
|
: T[K]['default'];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2691,6 +2703,8 @@ type InferMergedSettings<T extends SettingsSchema> = {
|
|||||||
? boolean
|
? boolean
|
||||||
: T[K]['default'] extends string
|
: T[K]['default'] extends string
|
||||||
? string
|
? string
|
||||||
|
: T[K]['default'] extends ReadonlyArray<infer U>
|
||||||
|
? U[]
|
||||||
: T[K]['default'];
|
: T[K]['default'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -481,6 +481,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
yolo: undefined,
|
yolo: undefined,
|
||||||
approvalMode: undefined,
|
approvalMode: undefined,
|
||||||
policy: undefined,
|
policy: undefined,
|
||||||
|
adminPolicy: undefined,
|
||||||
allowedMcpServerNames: undefined,
|
allowedMcpServerNames: undefined,
|
||||||
allowedTools: undefined,
|
allowedTools: undefined,
|
||||||
experimentalAcp: undefined,
|
experimentalAcp: undefined,
|
||||||
|
|||||||
@@ -29,3 +29,9 @@ exports[`ConfigInitDisplay > updates message on McpClientUpdate event 1`] = `
|
|||||||
Spinner Connecting to MCP servers... (1/2) - Waiting for: server2
|
Spinner Connecting to MCP servers... (1/2) - Waiting for: server2
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`ConfigInitDisplay > updates message on McpClientUpdate event 2`] = `
|
||||||
|
"
|
||||||
|
Spinner Connecting to MCP servers... (1/2) - Waiting for: server2
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -39,6 +39,26 @@ const __filename = fileURLToPath(import.meta.url);
|
|||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
export const DEFAULT_CORE_POLICIES_DIR = path.join(__dirname, 'policies');
|
export const DEFAULT_CORE_POLICIES_DIR = path.join(__dirname, 'policies');
|
||||||
|
|
||||||
|
// Cache to prevent duplicate warnings in the same process
|
||||||
|
const emittedWarnings = new Set<string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits a warning feedback event only once per process.
|
||||||
|
*/
|
||||||
|
function emitWarningOnce(message: string): void {
|
||||||
|
if (!emittedWarnings.has(message)) {
|
||||||
|
coreEvents.emitFeedback('warning', message);
|
||||||
|
emittedWarnings.add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the emitted warnings cache. Used primarily for tests.
|
||||||
|
*/
|
||||||
|
export function clearEmittedPolicyWarnings(): void {
|
||||||
|
emittedWarnings.clear();
|
||||||
|
}
|
||||||
|
|
||||||
// Policy tier constants for priority calculation
|
// Policy tier constants for priority calculation
|
||||||
export const DEFAULT_POLICY_TIER = 1;
|
export const DEFAULT_POLICY_TIER = 1;
|
||||||
export const EXTENSION_POLICY_TIER = 2;
|
export const EXTENSION_POLICY_TIER = 2;
|
||||||
@@ -89,33 +109,29 @@ export function getAlwaysAllowPriorityFraction(): number {
|
|||||||
* @param policyPaths Optional user-provided policy paths (from --policy flag).
|
* @param policyPaths Optional user-provided policy paths (from --policy flag).
|
||||||
* When provided, these replace the default user policies directory.
|
* When provided, these replace the default user policies directory.
|
||||||
* @param workspacePoliciesDir Optional path to a directory containing workspace policies.
|
* @param workspacePoliciesDir Optional path to a directory containing workspace policies.
|
||||||
|
* @param adminPolicyPaths Optional admin-provided policy paths (from --admin-policy flag).
|
||||||
|
* When provided, these supplement the default system policies directory.
|
||||||
*/
|
*/
|
||||||
export function getPolicyDirectories(
|
export function getPolicyDirectories(
|
||||||
defaultPoliciesDir?: string,
|
defaultPoliciesDir?: string,
|
||||||
policyPaths?: string[],
|
policyPaths?: string[],
|
||||||
workspacePoliciesDir?: string,
|
workspacePoliciesDir?: string,
|
||||||
|
adminPolicyPaths?: string[],
|
||||||
): string[] {
|
): string[] {
|
||||||
const dirs = [];
|
return [
|
||||||
|
|
||||||
// Admin tier (highest priority)
|
// Admin tier (highest priority)
|
||||||
dirs.push(Storage.getSystemPoliciesDir());
|
Storage.getSystemPoliciesDir(),
|
||||||
|
...(adminPolicyPaths ?? []),
|
||||||
|
|
||||||
// User tier (second highest priority)
|
// User tier (second highest priority)
|
||||||
if (policyPaths && policyPaths.length > 0) {
|
...(policyPaths ?? [Storage.getUserPoliciesDir()]),
|
||||||
dirs.push(...policyPaths);
|
|
||||||
} else {
|
|
||||||
dirs.push(Storage.getUserPoliciesDir());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workspace Tier (third highest)
|
// Workspace Tier (third highest)
|
||||||
if (workspacePoliciesDir) {
|
workspacePoliciesDir,
|
||||||
dirs.push(workspacePoliciesDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default tier (lowest priority)
|
// Default tier (lowest priority)
|
||||||
dirs.push(defaultPoliciesDir ?? DEFAULT_CORE_POLICIES_DIR);
|
defaultPoliciesDir ?? DEFAULT_CORE_POLICIES_DIR,
|
||||||
|
].filter((dir): dir is string => !!dir);
|
||||||
return dirs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,37 +140,40 @@ export function getPolicyDirectories(
|
|||||||
*/
|
*/
|
||||||
export function getPolicyTier(
|
export function getPolicyTier(
|
||||||
dir: string,
|
dir: string,
|
||||||
defaultPoliciesDir?: string,
|
context: {
|
||||||
workspacePoliciesDir?: string,
|
defaultPoliciesDir?: string;
|
||||||
|
workspacePoliciesDir?: string;
|
||||||
|
adminPolicyPaths?: Set<string>;
|
||||||
|
systemPoliciesDir: string;
|
||||||
|
userPoliciesDir: string;
|
||||||
|
},
|
||||||
): number {
|
): number {
|
||||||
const USER_POLICIES_DIR = Storage.getUserPoliciesDir();
|
|
||||||
const ADMIN_POLICIES_DIR = Storage.getSystemPoliciesDir();
|
|
||||||
|
|
||||||
const normalizedDir = path.resolve(dir);
|
const normalizedDir = path.resolve(dir);
|
||||||
const normalizedUser = path.resolve(USER_POLICIES_DIR);
|
|
||||||
const normalizedAdmin = path.resolve(ADMIN_POLICIES_DIR);
|
|
||||||
|
|
||||||
|
if (normalizedDir === context.systemPoliciesDir) {
|
||||||
|
return ADMIN_POLICY_TIER;
|
||||||
|
}
|
||||||
|
if (context.adminPolicyPaths?.has(normalizedDir)) {
|
||||||
|
return ADMIN_POLICY_TIER;
|
||||||
|
}
|
||||||
|
if (normalizedDir === context.userPoliciesDir) {
|
||||||
|
return USER_POLICY_TIER;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
defaultPoliciesDir &&
|
context.workspacePoliciesDir &&
|
||||||
normalizedDir === path.resolve(defaultPoliciesDir)
|
normalizedDir === path.resolve(context.workspacePoliciesDir)
|
||||||
|
) {
|
||||||
|
return WORKSPACE_POLICY_TIER;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
context.defaultPoliciesDir &&
|
||||||
|
normalizedDir === path.resolve(context.defaultPoliciesDir)
|
||||||
) {
|
) {
|
||||||
return DEFAULT_POLICY_TIER;
|
return DEFAULT_POLICY_TIER;
|
||||||
}
|
}
|
||||||
if (normalizedDir === path.resolve(DEFAULT_CORE_POLICIES_DIR)) {
|
if (normalizedDir === path.resolve(DEFAULT_CORE_POLICIES_DIR)) {
|
||||||
return DEFAULT_POLICY_TIER;
|
return DEFAULT_POLICY_TIER;
|
||||||
}
|
}
|
||||||
if (normalizedDir === normalizedUser) {
|
|
||||||
return USER_POLICY_TIER;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
workspacePoliciesDir &&
|
|
||||||
normalizedDir === path.resolve(workspacePoliciesDir)
|
|
||||||
) {
|
|
||||||
return WORKSPACE_POLICY_TIER;
|
|
||||||
}
|
|
||||||
if (normalizedDir === normalizedAdmin) {
|
|
||||||
return ADMIN_POLICY_TIER;
|
|
||||||
}
|
|
||||||
|
|
||||||
return DEFAULT_POLICY_TIER;
|
return DEFAULT_POLICY_TIER;
|
||||||
}
|
}
|
||||||
@@ -178,21 +197,24 @@ export function formatPolicyError(error: PolicyFileError): string {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters out insecure policy directories (specifically the system policy directory).
|
* Filters out insecure policy directories (specifically the system policy directory).
|
||||||
|
* Supplemental admin policy paths are NOT subject to strict security checks as they
|
||||||
|
* are explicitly provided by the user/administrator via flags or settings.
|
||||||
* Emits warnings if insecure directories are found.
|
* Emits warnings if insecure directories are found.
|
||||||
*/
|
*/
|
||||||
async function filterSecurePolicyDirectories(
|
async function filterSecurePolicyDirectories(
|
||||||
dirs: string[],
|
dirs: string[],
|
||||||
|
systemPoliciesDir: string,
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
const systemPoliciesDir = path.resolve(Storage.getSystemPoliciesDir());
|
|
||||||
|
|
||||||
const results = await Promise.all(
|
const results = await Promise.all(
|
||||||
dirs.map(async (dir) => {
|
dirs.map(async (dir) => {
|
||||||
// Only check security for system policies
|
const normalizedDir = path.resolve(dir);
|
||||||
if (path.resolve(dir) === systemPoliciesDir) {
|
const isSystemPolicy = normalizedDir === systemPoliciesDir;
|
||||||
|
|
||||||
|
if (isSystemPolicy) {
|
||||||
const { secure, reason } = await isDirectorySecure(dir);
|
const { secure, reason } = await isDirectorySecure(dir);
|
||||||
if (!secure) {
|
if (!secure) {
|
||||||
const msg = `Security Warning: Skipping system policies from ${dir}: ${reason}`;
|
const msg = `Security Warning: Skipping system policies from ${dir}: ${reason}`;
|
||||||
coreEvents.emitFeedback('warning', msg);
|
emitWarningOnce(msg);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,41 +293,71 @@ export async function createPolicyEngineConfig(
|
|||||||
approvalMode: ApprovalMode,
|
approvalMode: ApprovalMode,
|
||||||
defaultPoliciesDir?: string,
|
defaultPoliciesDir?: string,
|
||||||
): Promise<PolicyEngineConfig> {
|
): Promise<PolicyEngineConfig> {
|
||||||
|
const systemPoliciesDir = path.resolve(Storage.getSystemPoliciesDir());
|
||||||
|
const userPoliciesDir = path.resolve(Storage.getUserPoliciesDir());
|
||||||
|
let adminPolicyPaths = settings.adminPolicyPaths;
|
||||||
|
|
||||||
|
// Security: Ignore supplemental admin policies if the system directory already contains policies.
|
||||||
|
// This prevents flag-based overrides when a central system policy is established.
|
||||||
|
if (adminPolicyPaths?.length) {
|
||||||
|
try {
|
||||||
|
const files = await fs.readdir(systemPoliciesDir);
|
||||||
|
if (files.some((f) => f.endsWith('.toml'))) {
|
||||||
|
const msg = `Security Warning: Ignoring --admin-policy because system policies are already defined in ${systemPoliciesDir}`;
|
||||||
|
emitWarningOnce(msg);
|
||||||
|
adminPolicyPaths = undefined;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (!isNodeError(e) || e.code !== 'ENOENT') {
|
||||||
|
debugLogger.warn(
|
||||||
|
`Failed to check system policies in ${systemPoliciesDir}`,
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const policyDirs = getPolicyDirectories(
|
const policyDirs = getPolicyDirectories(
|
||||||
defaultPoliciesDir,
|
defaultPoliciesDir,
|
||||||
settings.policyPaths,
|
settings.policyPaths,
|
||||||
settings.workspacePoliciesDir,
|
settings.workspacePoliciesDir,
|
||||||
|
adminPolicyPaths,
|
||||||
);
|
);
|
||||||
const securePolicyDirs = await filterSecurePolicyDirectories(policyDirs);
|
|
||||||
|
|
||||||
const normalizedAdminPoliciesDir = path.resolve(
|
const adminPolicyPathsSet = adminPolicyPaths
|
||||||
Storage.getSystemPoliciesDir(),
|
? new Set(adminPolicyPaths.map((p) => path.resolve(p)))
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const securePolicyDirs = await filterSecurePolicyDirectories(
|
||||||
|
policyDirs,
|
||||||
|
systemPoliciesDir,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const tierContext = {
|
||||||
|
defaultPoliciesDir,
|
||||||
|
workspacePoliciesDir: settings.workspacePoliciesDir,
|
||||||
|
adminPolicyPaths: adminPolicyPathsSet,
|
||||||
|
systemPoliciesDir,
|
||||||
|
userPoliciesDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
const userProvidedPaths = settings.policyPaths
|
||||||
|
? new Set(settings.policyPaths.map((p) => path.resolve(p)))
|
||||||
|
: new Set<string>();
|
||||||
|
|
||||||
// Load policies from TOML files
|
// Load policies from TOML files
|
||||||
const {
|
const {
|
||||||
rules: tomlRules,
|
rules: tomlRules,
|
||||||
checkers: tomlCheckers,
|
checkers: tomlCheckers,
|
||||||
errors,
|
errors,
|
||||||
} = await loadPoliciesFromToml(securePolicyDirs, (p) => {
|
} = await loadPoliciesFromToml(securePolicyDirs, (p) => {
|
||||||
const tier = getPolicyTier(
|
|
||||||
p,
|
|
||||||
defaultPoliciesDir,
|
|
||||||
settings.workspacePoliciesDir,
|
|
||||||
);
|
|
||||||
|
|
||||||
// If it's a user-provided path that isn't already categorized as ADMIN,
|
|
||||||
// treat it as USER tier.
|
|
||||||
if (
|
|
||||||
settings.policyPaths?.some(
|
|
||||||
(userPath) => path.resolve(userPath) === path.resolve(p),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const normalizedPath = path.resolve(p);
|
const normalizedPath = path.resolve(p);
|
||||||
if (normalizedPath !== normalizedAdminPoliciesDir) {
|
const tier = getPolicyTier(normalizedPath, tierContext);
|
||||||
|
|
||||||
|
// If it's a user-provided path that isn't already categorized as ADMIN, treat it as USER tier.
|
||||||
|
if (userProvidedPaths.has(normalizedPath) && tier !== ADMIN_POLICY_TIER) {
|
||||||
return USER_POLICY_TIER;
|
return USER_POLICY_TIER;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return tier;
|
return tier;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -311,6 +311,8 @@ export interface PolicySettings {
|
|||||||
mcpServers?: Record<string, { trust?: boolean }>;
|
mcpServers?: Record<string, { trust?: boolean }>;
|
||||||
// User provided policies that will replace the USER level policies in ~/.gemini/policies
|
// User provided policies that will replace the USER level policies in ~/.gemini/policies
|
||||||
policyPaths?: string[];
|
policyPaths?: string[];
|
||||||
|
// Admin provided policies that will supplement the ADMIN level policies
|
||||||
|
adminPolicyPaths?: string[];
|
||||||
workspacePoliciesDir?: string;
|
workspacePoliciesDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,16 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"adminPolicyPaths": {
|
||||||
|
"title": "Admin Policy Paths",
|
||||||
|
"description": "Additional admin policy files or directories to load.",
|
||||||
|
"markdownDescription": "Additional admin policy files or directories to load.\n\n- Category: `Advanced`\n- Requires restart: `yes`\n- Default: `[]`",
|
||||||
|
"default": [],
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"title": "General",
|
"title": "General",
|
||||||
"description": "General application settings.",
|
"description": "General application settings.",
|
||||||
|
|||||||
Reference in New Issue
Block a user