1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! The sandbox and capability flag block shared by every command that launches
//! a run.
//!
//! `harn run` and `harn time run` execute the *same* script under the *same*
//! runtime; they differ only in what they report afterwards. Their confinement
//! surface must therefore be identical, and the way to guarantee that is for
//! there to be one declaration of it. Before this struct existed the two
//! commands each hand-declared the six sandbox flags, and `harn time run`
//! silently lacked the environment-policy flags that `harn run` had gained —
//! the predictable outcome of a duplicated surface, not an oversight anyone
//! could have caught by reading either file alone.
//!
//! Flatten this into a command's args with `#[command(flatten)]` and build the
//! run options with [`crate::commands::run::sandbox_options_from_args`]. A
//! new confinement flag then lands in one place and every launcher gets it.
use clap::Args;
use std::path::PathBuf;
#[derive(Clone, Debug, Default, Args)]
pub(crate) struct SandboxArgs {
/// Disable the default worktree filesystem/process sandbox and
/// network egress fail-closed guard for this run.
#[arg(long = "no-sandbox", action = clap::ArgAction::SetTrue)]
pub no_sandbox: bool,
/// Permit policy-managed child network while retaining the worktree
/// sandbox. Children reach public hosts; private and loopback addresses
/// stay denied, and HARN_EGRESS_* or harness.net.egress_policy narrows it.
#[arg(
long = "allow-process-network",
action = clap::ArgAction::SetTrue,
conflicts_with = "no_sandbox"
)]
pub allow_process_network: bool,
/// Permit child processes to bind and connect TCP loopback sockets while
/// retaining the external-egress deny. Unsupported OS backends fail closed.
#[arg(
long = "allow-process-loopback",
action = clap::ArgAction::SetTrue,
conflicts_with = "no_sandbox"
)]
pub allow_process_loopback: bool,
/// Extra read-only filesystem roots. Repeatable; each path is
/// readable but never writable.
#[arg(
long = "read-only-root",
value_name = "PATH",
conflicts_with = "no_sandbox"
)]
pub read_only_root: Vec<PathBuf>,
/// Extra writable filesystem roots. Repeatable; each path becomes
/// part of the run's write jail while sandboxing stays enabled.
#[arg(
long = "write-root",
visible_alias = "writable-root",
value_name = "PATH",
conflicts_with = "no_sandbox"
)]
pub write_root: Vec<PathBuf>,
/// Extra subprocess-only read roots. Repeatable; Harn filesystem builtins
/// do not gain access to these paths.
#[arg(
long = "sandbox-read-root",
value_name = "PATH",
conflicts_with = "no_sandbox"
)]
pub sandbox_read_root: Vec<PathBuf>,
/// Extra subprocess-only write roots. Repeatable; Harn filesystem builtins
/// do not gain access to these paths.
#[arg(
long = "sandbox-write-root",
value_name = "PATH",
conflicts_with = "no_sandbox"
)]
pub sandbox_write_root: Vec<PathBuf>,
/// Directories under which subprocesses may serve Unix-domain sockets
/// (build servers, compiler daemons). Repeatable; grants no IP
/// networking. Where a backend cannot scope a connection by path it
/// grants the serving half only and refuses to connect. Unsupported OS
/// backends fail closed.
#[arg(
long = "sandbox-unix-socket-root",
value_name = "PATH",
conflicts_with = "no_sandbox"
)]
pub sandbox_unix_socket_root: Vec<PathBuf>,
/// Let subprocesses enumerate their own entries in the process
/// filesystem, which some managed runtimes need in order to identify
/// themselves during startup. Read-only, grants no network authority,
/// and fails closed on a kernel that cannot keep a sandboxed task from
/// inspecting its neighbours.
#[arg(
long = "sandbox-allow-process-self-introspection",
conflicts_with = "no_sandbox"
)]
pub sandbox_allow_process_self_introspection: bool,
/// Absolute path to the installed helper that builds a private network
/// namespace for a confined child, which is how loopback-only child
/// networking is rendered on backends that cannot express it any other
/// way. Supplied rather than derived: on hosts that restrict
/// unprivileged namespaces the permission is granted per executable path
/// by host policy, and that grant has to name one stable installed file.
/// Without it a loopback grant is refused, never weakened.
#[arg(
long = "netns-launcher",
value_name = "PATH",
conflicts_with = "no_sandbox"
)]
pub netns_launcher: Option<String>,
/// Session environment: `inherited` snapshots the launcher (default),
/// `isolated` admits runtime essentials only, and `granted` adds the
/// declared `--grant` set. This is independent of the filesystem sandbox.
#[arg(long = "environment-policy", value_enum, value_name = "POLICY")]
pub environment_policy: Option<crate::commands::run::EnvironmentPolicyArg>,
/// Grant one named credential to this session. Repeatable.
///
/// `NAME=SOURCE[,expose=ENV_VAR][,for=COMMAND][,to=in_process]`, where
/// `SOURCE` is `env:VAR_NAME` (a launcher variable, snapshotted at
/// launch) or `secret://ACCOUNT/KEY` (a secret-store pointer). The
/// optional `,expose=ENV_VAR` publishes the value as `ENV_VAR`. Without
/// `,for=`, that exposure is session-scoped (spawned commands and this
/// run's own model calls). With `,for=COMMAND`, only spawns whose
/// executable basename matches `COMMAND` see the variable. With
/// `,to=in_process`, only this run's own model calls and `harness.env`
/// see it, and no spawned command does. Any `--grant` selects the
/// `granted` policy unless the policy is explicit. Nothing else from the
/// launcher environment crosses the boundary. For example:
/// `--grant gh_token=secret://gh/token,expose=GH_TOKEN,for=gh`.
#[arg(long = "grant", value_name = "SPEC")]
pub grant: Vec<String>,
}