Harden isolate
This commit is contained in:
1 parent
ae7109ac7c
commit
d579c12080
1 file changed
+68
-28
@@ -1,34 +1,44 @@
|
|||||||
#/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
project_dir=$( pwd )
|
project_dir=$( pwd -P ) || exit 1
|
||||||
|
home_dir=$( readlink -e -- "${HOME}" ) || exit 1
|
||||||
|
|
||||||
bubblewrap=$( command -v bwrap )
|
if ! bubblewrap=$( command -v bwrap ); then
|
||||||
|
echo "bubblewrap (bwrap) is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
agents=(
|
agents=(
|
||||||
$( command -v claude )
|
$( command -v claude )
|
||||||
|
$( command -v codex )
|
||||||
$( command -v pi )
|
$( command -v pi )
|
||||||
$( command -v ps ) # for testing
|
$( command -v ps ) # for testing
|
||||||
|
$( command -v ls ) # for testing
|
||||||
|
$( command -v touch ) # for testing
|
||||||
|
$( command -v bash ) # for testing
|
||||||
)
|
)
|
||||||
|
|
||||||
if [[ "${project_dir}" = "${HOME}" ]]; then
|
if [[ "${project_dir}" = "${home_dir}" ]]; then
|
||||||
echo "Not running in ${HOME}"
|
echo "Not running in ${HOME}" >&2
|
||||||
exit
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "${1}" ]] && [[ -n $( command -v "${1}" ) ]]; then
|
if [[ -n "${1}" ]] && [[ -n $( command -v "${1}" ) ]]; then
|
||||||
coder=$( command -v "${1}" ) && shift
|
coder=$( command -v "${1}" ) && shift
|
||||||
args="${@}"
|
|
||||||
else
|
else
|
||||||
echo "Please specify a coding agent"
|
echo "Please specify a coding agent" >&2
|
||||||
exit
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! [[ " ${agents[@]} " =~ " ${coder} " ]]; then
|
if ! [[ " ${agents[@]} " =~ " ${coder} " ]]; then
|
||||||
echo "No agent found"
|
echo "No agent found" >&2
|
||||||
exit
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
declare -a ro_binds
|
declare -a ro_binds
|
||||||
declare -a binds
|
declare -a rw_binds
|
||||||
|
declare -a bwrap_args
|
||||||
|
declare -a bind_sources
|
||||||
|
declare -a bind_destinations
|
||||||
|
|
||||||
ro_binds=(
|
ro_binds=(
|
||||||
/usr
|
/usr
|
||||||
@@ -40,32 +50,62 @@ ro_binds=(
|
|||||||
/etc/ssl
|
/etc/ssl
|
||||||
/etc/passwd
|
/etc/passwd
|
||||||
/etc/group
|
/etc/group
|
||||||
|
"${HOME}/sources"
|
||||||
)
|
)
|
||||||
|
|
||||||
for loc in "${HOME}/.gitconfig" "${HOME}/.local"; do
|
for readonly_path in "${HOME}/.gitconfig" "${HOME}/bin" "${HOME}/.local" "${HOME}/.atuin" "${XDG_RUNTIME_DIR}"; do
|
||||||
if [[ -e "${loc}" ]]; then
|
if [[ -e "${readonly_path}" ]]; then
|
||||||
ro_binds+=("${loc}")
|
ro_binds+=("${readonly_path}")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
binds=()
|
for readwrite_path in "${HOME}/.claude" "${HOME}/.claude.json" "${HOME}/.local/share/claude" "${HOME}/.pi" "${project_dir}"; do
|
||||||
|
if [[ -e "${readwrite_path}" ]]; then
|
||||||
for loc in "${HOME}/.claude" "${HOME}/.pi" "${project_dir}"; do
|
rw_binds+=("${readwrite_path}")
|
||||||
if [[ -e "${loc}" ]]; then
|
|
||||||
binds+=("${loc}")
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
for i in "${!ro_binds[@]}"; do
|
# Resolve mount sources, but keep the paths applications expect as destinations.
|
||||||
ro_binds["${i}"]="--ro-bind ${ro_binds[${i}]} ${ro_binds[${i}]}"
|
for readonly_path in "${ro_binds[@]}"; do
|
||||||
|
bind_source=$( readlink -e -- "${readonly_path}" ) || exit 1
|
||||||
|
bwrap_args+=("--ro-bind" "${bind_source}" "${readonly_path}")
|
||||||
|
bind_sources+=("${bind_source}")
|
||||||
|
bind_destinations+=("${readonly_path}")
|
||||||
done
|
done
|
||||||
for i in "${!binds[@]}"; do
|
for readwrite_path in "${rw_binds[@]}"; do
|
||||||
binds["${i}"]="--bind ${binds[${i}]} ${binds[${i}]}"
|
bind_source=$( readlink -e -- "${readwrite_path}" ) || exit 1
|
||||||
|
bwrap_args+=("--bind" "${bind_source}" "${readwrite_path}")
|
||||||
|
bind_sources+=("${bind_source}")
|
||||||
|
bind_destinations+=("${readwrite_path}")
|
||||||
done
|
done
|
||||||
|
|
||||||
${bubblewrap} \
|
git_config="${project_dir}/.git/config"
|
||||||
${ro_binds[@]} \
|
git_worktree_config="${project_dir}/.git/config.worktree"
|
||||||
${binds[@]} \
|
if [[ -f "${project_dir}/.git" ]]; then
|
||||||
|
# Linked worktrees keep their shared and per-worktree config elsewhere.
|
||||||
|
git_config=$( git -C "${project_dir}" rev-parse --path-format=absolute --git-path config ) || exit 1
|
||||||
|
git_worktree_config=$( git -C "${project_dir}" rev-parse --path-format=absolute --git-path config.worktree ) || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mask secrets after all directory mounts so later binds cannot expose them.
|
||||||
|
for hide_path in "${git_config}" "${git_worktree_config}" "${project_dir}/.env"; do
|
||||||
|
if [[ -e "${hide_path}" ]]; then
|
||||||
|
hide_path=$( readlink -e -- "${hide_path}" ) || exit 1
|
||||||
|
bwrap_args+=("--ro-bind" /dev/null "${hide_path}")
|
||||||
|
# A symlinked mount can expose the same file at another destination.
|
||||||
|
for i in "${!bind_sources[@]}"; do
|
||||||
|
if [[ "${hide_path}" = "${bind_sources[${i}]}" || "${hide_path}" = "${bind_sources[${i}]}/"* ]]; then
|
||||||
|
mask_path="${bind_destinations[${i}]}${hide_path#"${bind_sources[${i}]}"}"
|
||||||
|
if [[ "${mask_path}" != "${hide_path}" ]]; then
|
||||||
|
bwrap_args+=("--ro-bind" /dev/null "${mask_path}")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
"${bubblewrap}" \
|
||||||
|
"${bwrap_args[@]}" \
|
||||||
--tmpfs /tmp \
|
--tmpfs /tmp \
|
||||||
--proc /proc \
|
--proc /proc \
|
||||||
--dev /dev \
|
--dev /dev \
|
||||||
@@ -73,4 +113,4 @@ ${bubblewrap} \
|
|||||||
--unshare-pid \
|
--unshare-pid \
|
||||||
--die-with-parent \
|
--die-with-parent \
|
||||||
--chdir "${project_dir}" \
|
--chdir "${project_dir}" \
|
||||||
"$( command -v ${coder} )" "${args}"
|
"${coder}" "${@}"
|
||||||
Reference in new issue
Block a user