Skip to content

Commit fb9849e

Browse files
authored
migrating execpolicy -> execpolicy-legacy and execpolicy2 -> execpolicy (#6956)
1 parent 72a1453 commit fb9849e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+933
-926
lines changed

codex-rs/Cargo.lock

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ members = [
1818
"exec",
1919
"exec-server",
2020
"execpolicy",
21-
"execpolicy2",
21+
"execpolicy-legacy",
2222
"keyring-store",
2323
"file-search",
2424
"linux-sandbox",
@@ -67,7 +67,7 @@ codex-chatgpt = { path = "chatgpt" }
6767
codex-common = { path = "common" }
6868
codex-core = { path = "core" }
6969
codex-exec = { path = "exec" }
70-
codex-execpolicy2 = { path = "execpolicy2" }
70+
codex-execpolicy = { path = "execpolicy" }
7171
codex-feedback = { path = "feedback" }
7272
codex-file-search = { path = "file-search" }
7373
codex-git = { path = "utils/git" }

codex-rs/core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ chrono = { workspace = true, features = ["serde"] }
2222
codex-app-server-protocol = { workspace = true }
2323
codex-apply-patch = { workspace = true }
2424
codex-async-utils = { workspace = true }
25-
codex-execpolicy2 = { workspace = true }
25+
codex-execpolicy = { workspace = true }
2626
codex-file-search = { workspace = true }
2727
codex-git = { workspace = true }
2828
codex-keyring-store = { workspace = true }

codex-rs/core/src/codex.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ use crate::user_instructions::UserInstructions;
121121
use crate::user_notification::UserNotification;
122122
use crate::util::backoff;
123123
use codex_async_utils::OrCancelExt;
124-
use codex_execpolicy2::Policy as ExecPolicy;
124+
use codex_execpolicy::Policy as ExecPolicy;
125125
use codex_otel::otel_event_manager::OtelEventManager;
126126
use codex_protocol::config_types::ReasoningEffort as ReasoningEffortConfig;
127127
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
@@ -2616,7 +2616,7 @@ mod tests {
26162616
cwd: config.cwd.clone(),
26172617
original_config_do_not_use: Arc::clone(&config),
26182618
features: Features::default(),
2619-
exec_policy: Arc::new(codex_execpolicy2::Policy::empty()),
2619+
exec_policy: Arc::new(ExecPolicy::empty()),
26202620
session_source: SessionSource::Exec,
26212621
};
26222622

@@ -2694,7 +2694,7 @@ mod tests {
26942694
cwd: config.cwd.clone(),
26952695
original_config_do_not_use: Arc::clone(&config),
26962696
features: Features::default(),
2697-
exec_policy: Arc::new(codex_execpolicy2::Policy::empty()),
2697+
exec_policy: Arc::new(ExecPolicy::empty()),
26982698
session_source: SessionSource::Exec,
26992699
};
27002700

codex-rs/core/src/exec_policy.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ use std::path::PathBuf;
44
use std::sync::Arc;
55

66
use crate::command_safety::is_dangerous_command::requires_initial_appoval;
7-
use codex_execpolicy2::Decision;
8-
use codex_execpolicy2::Evaluation;
9-
use codex_execpolicy2::Policy;
10-
use codex_execpolicy2::PolicyParser;
7+
use codex_execpolicy::Decision;
8+
use codex_execpolicy::Evaluation;
9+
use codex_execpolicy::Policy;
10+
use codex_execpolicy::PolicyParser;
1111
use codex_protocol::protocol::AskForApproval;
1212
use codex_protocol::protocol::SandboxPolicy;
1313
use thiserror::Error;
@@ -41,7 +41,7 @@ pub enum ExecPolicyError {
4141
#[error("failed to parse execpolicy file {path}: {source}")]
4242
ParsePolicy {
4343
path: String,
44-
source: codex_execpolicy2::Error,
44+
source: codex_execpolicy::Error,
4545
},
4646
}
4747

codex-rs/exec-server/src/posix.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ pub async fn main_execve_wrapper() -> anyhow::Result<()> {
144144
std::process::exit(exit_code);
145145
}
146146

147-
// TODO: replace with execpolicy2
147+
// TODO: replace with execpolicy
148148

149149
fn dummy_exec_policy(file: &Path, argv: &[String], _workdir: &Path) -> ExecPolicyOutcome {
150150
if file.ends_with("rm") {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[package]
2+
edition = "2024"
3+
name = "codex-execpolicy-legacy"
4+
description = "Legacy exec policy engine for validating proposed exec calls."
5+
version = { workspace = true }
6+
7+
[[bin]]
8+
name = "codex-execpolicy-legacy"
9+
path = "src/main.rs"
10+
11+
[lib]
12+
name = "codex_execpolicy_legacy"
13+
path = "src/lib.rs"
14+
15+
[lints]
16+
workspace = true
17+
18+
[dependencies]
19+
allocative = { workspace = true }
20+
anyhow = { workspace = true }
21+
clap = { workspace = true, features = ["derive"] }
22+
derive_more = { workspace = true, features = ["display"] }
23+
env_logger = { workspace = true }
24+
log = { workspace = true }
25+
multimap = { workspace = true }
26+
path-absolutize = { workspace = true }
27+
regex-lite = { workspace = true }
28+
serde = { workspace = true, features = ["derive"] }
29+
serde_json = { workspace = true }
30+
serde_with = { workspace = true, features = ["macros"] }
31+
starlark = { workspace = true }
32+
33+
[dev-dependencies]
34+
tempfile = { workspace = true }
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# codex-execpolicy-legacy
2+
3+
This crate hosts the original execpolicy implementation. The newer prefix-rule
4+
engine lives in `codex-execpolicy`.
5+
6+
The goal of this library is to classify a proposed [`execv(3)`](https://linux.die.net/man/3/execv) command into one of the following states:
7+
8+
- `safe` The command is safe to run (\*).
9+
- `match` The command matched a rule in the policy, but the caller should decide whether it is safe to run based on the files it will write.
10+
- `forbidden` The command is not allowed to be run.
11+
- `unverified` The safety cannot be determined: make the user decide.
12+
13+
(\*) Whether an `execv(3)` call should be considered "safe" often requires additional context beyond the arguments to `execv()` itself. For example, if you trust an autonomous software agent to write files in your source tree, then deciding whether `/bin/cp foo bar` is "safe" depends on `getcwd(3)` for the calling process as well as the `realpath` of `foo` and `bar` when resolved against `getcwd()`.
14+
To that end, rather than returning a boolean, the validator returns a structured result that the client is expected to use to determine the "safety" of the proposed `execv()` call.
15+
16+
For example, to check the command `ls -l foo`, the checker would be invoked as follows:
17+
18+
```shell
19+
cargo run -p codex-execpolicy-legacy -- check ls -l foo | jq
20+
```
21+
22+
It will exit with `0` and print the following to stdout:
23+
24+
```json
25+
{
26+
"result": "safe",
27+
"match": {
28+
"program": "ls",
29+
"flags": [
30+
{
31+
"name": "-l"
32+
}
33+
],
34+
"opts": [],
35+
"args": [
36+
{
37+
"index": 1,
38+
"type": "ReadableFile",
39+
"value": "foo"
40+
}
41+
],
42+
"system_path": ["/bin/ls", "/usr/bin/ls"]
43+
}
44+
}
45+
```
46+
47+
Of note:
48+
49+
- `foo` is tagged as a `ReadableFile`, so the caller should resolve `foo` relative to `getcwd()` and `realpath` it (as it may be a symlink) to determine whether `foo` is safe to read.
50+
- While the specified executable is `ls`, `"system_path"` offers `/bin/ls` and `/usr/bin/ls` as viable alternatives to avoid using whatever `ls` happens to appear first on the user's `$PATH`. If either exists on the host, it is recommended to use it as the first argument to `execv(3)` instead of `ls`.
51+
52+
Further, "safety" in this system is not a guarantee that the command will execute successfully. As an example, `cat /Users/mbolin/code/codex/README.md` may be considered "safe" if the system has decided the agent is allowed to read anything under `/Users/mbolin/code/codex`, but it will fail at runtime if `README.md` does not exist. (Though this is "safe" in that the agent did not read any files that it was not authorized to read.)
53+
54+
## Policy
55+
56+
Currently, the default policy is defined in [`default.policy`](./src/default.policy) within the crate.
57+
58+
The system uses [Starlark](https://bazel.build/rules/language) as the file format because, unlike something like JSON or YAML, it supports "macros" without compromising on safety or reproducibility. (Under the hood, we use [`starlark-rust`](https://github.com/facebook/starlark-rust) as the specific Starlark implementation.)
59+
60+
This policy contains "rules" such as:
61+
62+
```python
63+
define_program(
64+
program="cp",
65+
options=[
66+
flag("-r"),
67+
flag("-R"),
68+
flag("--recursive"),
69+
],
70+
args=[ARG_RFILES, ARG_WFILE],
71+
system_path=["/bin/cp", "/usr/bin/cp"],
72+
should_match=[
73+
["foo", "bar"],
74+
],
75+
should_not_match=[
76+
["foo"],
77+
],
78+
)
79+
```
80+
81+
This rule means that:
82+
83+
- `cp` can be used with any of the following flags (where "flag" means "an option that does not take an argument"): `-r`, `-R`, `--recursive`.
84+
- The initial `ARG_RFILES` passed to `args` means that it expects one or more arguments that correspond to "readable files"
85+
- The final `ARG_WFILE` passed to `args` means that it expects exactly one argument that corresponds to a "writeable file."
86+
- As a means of a lightweight way of including a unit test alongside the definition, the `should_match` list is a list of examples of `execv(3)` args that should match the rule and `should_not_match` is a list of examples that should not match. These examples are verified when the `.policy` file is loaded.
87+
88+
Note that the language of the `.policy` file is still evolving, as we have to continue to expand it so it is sufficiently expressive to accept all commands we want to consider "safe" without allowing unsafe commands to pass through.
89+
90+
The integrity of `default.policy` is verified [via unit tests](./tests).
91+
92+
Further, the CLI supports a `--policy` option to specify a custom `.policy` file for ad-hoc testing.
93+
94+
## Output Type: `match`
95+
96+
Going back to the `cp` example, because the rule matches an `ARG_WFILE`, it will return `match` instead of `safe`:
97+
98+
```shell
99+
cargo run -p codex-execpolicy-legacy -- check cp src1 src2 dest | jq
100+
```
101+
102+
If the caller wants to consider allowing this command, it should parse the JSON to pick out the `WriteableFile` arguments and decide whether they are safe to write:
103+
104+
```json
105+
{
106+
"result": "match",
107+
"match": {
108+
"program": "cp",
109+
"flags": [],
110+
"opts": [],
111+
"args": [
112+
{
113+
"index": 0,
114+
"type": "ReadableFile",
115+
"value": "src1"
116+
},
117+
{
118+
"index": 1,
119+
"type": "ReadableFile",
120+
"value": "src2"
121+
},
122+
{
123+
"index": 2,
124+
"type": "WriteableFile",
125+
"value": "dest"
126+
}
127+
],
128+
"system_path": ["/bin/cp", "/usr/bin/cp"]
129+
}
130+
}
131+
```
132+
133+
Note the exit code is still `0` for a `match` unless the `--require-safe` flag is specified, in which case the exit code is `12`.
134+
135+
## Output Type: `forbidden`
136+
137+
It is also possible to define a rule that, if it matches a command, should flag it as _forbidden_. For example, we do not want agents to be able to run `applied deploy` _ever_, so we define the following rule:
138+
139+
```python
140+
define_program(
141+
program="applied",
142+
args=["deploy"],
143+
forbidden="Infrastructure Risk: command contains 'applied deploy'",
144+
should_match=[
145+
["deploy"],
146+
],
147+
should_not_match=[
148+
["lint"],
149+
],
150+
)
151+
```
152+
153+
Note that for a rule to be forbidden, the `forbidden` keyword arg must be specified as the reason the command is forbidden. This will be included in the output:
154+
155+
```shell
156+
cargo run -p codex-execpolicy-legacy -- check applied deploy | jq
157+
```
158+
159+
```json
160+
{
161+
"result": "forbidden",
162+
"reason": "Infrastructure Risk: command contains 'applied deploy'",
163+
"cause": {
164+
"Exec": {
165+
"exec": {
166+
"program": "applied",
167+
"flags": [],
168+
"opts": [],
169+
"args": [
170+
{
171+
"index": 0,
172+
"type": {
173+
"Literal": "deploy"
174+
},
175+
"value": "deploy"
176+
}
177+
],
178+
"system_path": []
179+
}
180+
}
181+
}
182+
}
183+
```
File renamed without changes.

0 commit comments

Comments
 (0)