Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions plugins/claude-code/scripts/run-sleep.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,9 @@ elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -d "$CLAUDE_PLUGIN_ROOT/../../skillop
REPO_ROOT="$(cd "$CLAUDE_PLUGIN_ROOT/../.." && pwd)"
elif [ -n "${SKILLOPT_SLEEP_REPO:-}" ] && [ -d "$SKILLOPT_SLEEP_REPO/skillopt_sleep" ]; then
REPO_ROOT="$SKILLOPT_SLEEP_REPO"
else
# last resort: search upward from CWD
d="$PWD"
while [ "$d" != "/" ]; do
[ -d "$d/skillopt_sleep" ] && { REPO_ROOT="$d"; break; }
d="$(dirname "$d")"
done
fi
# Deliberately no search upward from CWD: the CWD is the (possibly untrusted)
# project being worked on, and a skillopt_sleep/ planted there would be run.
if [ "$#" -eq 0 ]; then set -- status; fi

if [ -n "${REPO_ROOT:-}" ]; then
Expand Down Expand Up @@ -59,18 +54,23 @@ if command -v skillopt-sleep >/dev/null 2>&1; then
fi

# Fallback 2: importable as a module (pip install into the active Python).
# Pick a Python >= 3.10 and check importability.
# Pick a Python >= 3.10 and check importability. `python -c`/`-m` put the CWD
# (the user's project) first on sys.path, so a planted skillopt_sleep.py there
# would run; strip it before importing. The CWD itself is kept because the
# engine uses it as the default project.
NO_CWD_IMPORT="import sys; sys.path[:] = [p for p in sys.path if p]; import skillopt_sleep"
NO_CWD_RUN="import sys; sys.path[:] = [p for p in sys.path if p]; import runpy; runpy.run_module('skillopt_sleep', run_name='__main__', alter_sys=True)"
PY=""
for cand in python3.12 python3.11 python3.10 python3; do
if command -v "$cand" >/dev/null 2>&1; then
ver="$("$cand" -c 'import sys; print("%d%d" % sys.version_info[:2])' 2>/dev/null || echo 0)"
if [ "${ver:-0}" -ge 310 ] && "$cand" -c "import skillopt_sleep" >/dev/null 2>&1; then
if [ "${ver:-0}" -ge 310 ] && "$cand" -c "$NO_CWD_IMPORT" >/dev/null 2>&1; then
PY="$cand"; break
fi
fi
done
if [ -n "$PY" ]; then
exec "$PY" -m skillopt_sleep "$@"
exec "$PY" -c "$NO_CWD_RUN" "$@"
fi

echo "[sleep] ERROR: could not locate the skillopt_sleep package." >&2
Expand Down
29 changes: 12 additions & 17 deletions plugins/run-sleep.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,9 @@ if not "%SKILLOPT_SLEEP_REPO%"=="" if exist "%SKILLOPT_SLEEP_REPO%\skillopt_slee
goto root_resolved
)

:: Search upward from current directory
set "d=%CD%"
:loop
if exist "!d!\skillopt_sleep" (
set "REPO_ROOT=!d!"
goto root_resolved
)
for %%I in ("!d!") do set "parent=%%~dpI"
:: Strip trailing backslash from parent if it's not root
if "!parent!"=="!d!" goto root_resolved
set "parent=!parent:~0,-1!"
if "!parent!"=="" goto root_resolved
set "d=!parent!"
goto loop
:: Deliberately no search upward from the current directory: it is the
:: (possibly untrusted) project being worked on, and a skillopt_sleep folder
:: planted there would be run.

:root_resolved

Expand Down Expand Up @@ -90,13 +79,19 @@ exit /b !errorlevel!

:try_fallback_2
:: Fallback 2: importable as a module (pip install into the active Python).
:: `python -c`/`-m` put the current directory (the user's project) first on
:: sys.path, so a planted skillopt_sleep.py there would run; strip it before
:: importing. The current directory itself is kept because the engine uses it
:: as the default project.
set "NO_CWD_IMPORT=import sys; sys.path[:] = [p for p in sys.path if p]; import skillopt_sleep"
set "NO_CWD_RUN=import sys; sys.path[:] = [p for p in sys.path if p]; import runpy; runpy.run_module('skillopt_sleep', run_name='__main__', alter_sys=True)"
set "PY="
for %%p in (python3.exe python.exe py.exe) do (
where %%p >nul 2>nul
if !errorlevel! equ 0 (
%%p -c "import sys; sys.exit(0 if sys.version_info >= (3, 10) else 1)" >nul 2>nul
if !errorlevel! equ 0 (
%%p -c "import skillopt_sleep" >nul 2>nul
%%p -c "!NO_CWD_IMPORT!" >nul 2>nul
if !errorlevel! equ 0 (
set "PY=%%p"
goto py_import_found
Expand All @@ -109,9 +104,9 @@ for %%p in (python3.exe python.exe py.exe) do (
if "%PY%" == "" goto not_found

if "%~1" == "" (
"%PY%" -m skillopt_sleep status
"%PY%" -c "!NO_CWD_RUN!" status
) else (
"%PY%" -m skillopt_sleep %*
"%PY%" -c "!NO_CWD_RUN!" %*
)
exit /b !errorlevel!

Expand Down
23 changes: 11 additions & 12 deletions plugins/run-sleep.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,10 @@ if (Test-Path (Join-Path $ScriptDir "..\skillopt_sleep")) {
$RepoRoot = Resolve-Path (Join-Path $env:CLAUDE_PLUGIN_ROOT "..\..")
} elseif ($env:SKILLOPT_SLEEP_REPO -and (Test-Path (Join-Path $env:SKILLOPT_SLEEP_REPO "skillopt_sleep"))) {
$RepoRoot = Resolve-Path $env:SKILLOPT_SLEEP_REPO
} else {
# search upward from current location
$d = Get-Item .
while ($d -and $d.FullName -ne $d.Root.FullName) {
if (Test-Path (Join-Path $d.FullName "skillopt_sleep")) {
$RepoRoot = $d.FullName
break
}
$d = Split-Path -Parent $d.FullName -ErrorAction SilentlyContinue | Get-Item -ErrorAction SilentlyContinue
}
}
# Deliberately no search upward from the current location: it is the (possibly
# untrusted) project being worked on, and a skillopt_sleep folder planted there
# would be run.

$argsList = @($args)
if ($argsList.Count -eq 0) {
Expand Down Expand Up @@ -68,13 +61,19 @@ if ($cliCmd) {
}

# Fallback 2: importable as a module (pip install into the active Python).
# `python -c`/`-m` put the current directory (the user's project) first on
# sys.path, so a planted skillopt_sleep.py there would run; strip it before
# importing. The current directory itself is kept because the engine uses it as
# the default project.
$NoCwdImport = "import sys; sys.path[:] = [p for p in sys.path if p]; import skillopt_sleep"
$NoCwdRun = "import sys; sys.path[:] = [p for p in sys.path if p]; import runpy; runpy.run_module('skillopt_sleep', run_name='__main__', alter_sys=True)"
$Py = ""
foreach ($cand in @("python3", "python", "py")) {
$cmd = Get-Command $cand -ErrorAction SilentlyContinue
if ($cmd) {
$ver = & $cand -c "import sys; print('%d%d' % sys.version_info[:2])" 2>$null
if ($ver -and [int]$ver -ge 310) {
$hasModule = & $cand -c "import skillopt_sleep" 2>$null
$hasModule = & $cand -c $NoCwdImport 2>$null
if ($LASTEXITCODE -eq 0) {
$Py = $cand
break
Expand All @@ -84,7 +83,7 @@ foreach ($cand in @("python3", "python", "py")) {
}

if ($Py) {
& $Py -m skillopt_sleep $argsList
& $Py -c $NoCwdRun $argsList
exit $LASTEXITCODE
}

Expand Down
20 changes: 10 additions & 10 deletions plugins/run-sleep.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,9 @@ elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -d "$CLAUDE_PLUGIN_ROOT/../../skillop
REPO_ROOT="$(cd "$CLAUDE_PLUGIN_ROOT/../.." && pwd)"
elif [ -n "${SKILLOPT_SLEEP_REPO:-}" ] && [ -d "$SKILLOPT_SLEEP_REPO/skillopt_sleep" ]; then
REPO_ROOT="$SKILLOPT_SLEEP_REPO"
else
# last resort: search upward from CWD
d="$PWD"
while [ "$d" != "/" ]; do
[ -d "$d/skillopt_sleep" ] && { REPO_ROOT="$d"; break; }
d="$(dirname "$d")"
done
fi
# Deliberately no search upward from CWD: the CWD is the (possibly untrusted)
# project being worked on, and a skillopt_sleep/ planted there would be run.
if [ "$#" -eq 0 ]; then set -- status; fi

if [ -n "${REPO_ROOT:-}" ]; then
Expand Down Expand Up @@ -59,18 +54,23 @@ if command -v skillopt-sleep >/dev/null 2>&1; then
fi

# Fallback 2: importable as a module (pip install into the active Python).
# Pick a Python >= 3.10 and check importability.
# Pick a Python >= 3.10 and check importability. `python -c`/`-m` put the CWD
# (the user's project) first on sys.path, so a planted skillopt_sleep.py there
# would run; strip it before importing. The CWD itself is kept because the
# engine uses it as the default project.
NO_CWD_IMPORT="import sys; sys.path[:] = [p for p in sys.path if p]; import skillopt_sleep"
NO_CWD_RUN="import sys; sys.path[:] = [p for p in sys.path if p]; import runpy; runpy.run_module('skillopt_sleep', run_name='__main__', alter_sys=True)"
PY=""
for cand in python3.12 python3.11 python3.10 python3; do
if command -v "$cand" >/dev/null 2>&1; then
ver="$("$cand" -c 'import sys; print("%d%d" % sys.version_info[:2])' 2>/dev/null || echo 0)"
if [ "${ver:-0}" -ge 310 ] && "$cand" -c "import skillopt_sleep" >/dev/null 2>&1; then
if [ "${ver:-0}" -ge 310 ] && "$cand" -c "$NO_CWD_IMPORT" >/dev/null 2>&1; then
PY="$cand"; break
fi
fi
done
if [ -n "$PY" ]; then
exec "$PY" -m skillopt_sleep "$@"
exec "$PY" -c "$NO_CWD_RUN" "$@"
fi

echo "[sleep] ERROR: could not locate the skillopt_sleep package." >&2
Expand Down
45 changes: 45 additions & 0 deletions tests/test_run_sleep_fallback.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,51 @@ def test_error_message_mentions_install_options(self):
self.assertIn("SKILLOPT_SLEEP_REPO", err)


@unittest.skipIf(sys.platform == "win32", "Unix bash runner tests require POSIX/bash environment")
class TestRunnerIgnoresCwdPlants(unittest.TestCase):
"""Files planted in the (untrusted) project directory must never be run."""

PLANT = (
"import os\n"
"open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'PWNED'), 'w').close()\n"
)

def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
# Runner copied outside the repo so no source checkout is resolvable.
self._script = os.path.join(self._tmp.name, "runner", "run-sleep.sh")
os.makedirs(os.path.dirname(self._script))
shutil.copy2(RUNNER, self._script)
self._project = os.path.join(self._tmp.name, "project")
os.makedirs(self._project)
# PATH with a Python >= 3.10 but no skillopt-sleep CLI.
self._bindir = os.path.join(self._tmp.name, "bin")
os.makedirs(self._bindir)
os.symlink(sys.executable, os.path.join(self._bindir, "python3"))
self._env = {
"PATH": self._bindir + os.pathsep + "/usr/bin:/bin",
"HOME": os.environ.get("HOME", "/tmp"),
}

def tearDown(self):
self._tmp.cleanup()

def test_planted_module_file_in_cwd_not_imported(self):
with open(os.path.join(self._project, "skillopt_sleep.py"), "w") as f:
f.write(self.PLANT)
_run(self._script, ["status"], self._env, self._project)
self.assertFalse(os.path.exists(os.path.join(self._project, "PWNED")))

def test_planted_package_dir_in_cwd_not_used_as_repo(self):
pkg = os.path.join(self._project, "skillopt_sleep")
os.makedirs(pkg)
for name in ("__init__.py", "__main__.py"):
with open(os.path.join(pkg, name), "w") as f:
f.write(self.PLANT)
_run(self._script, ["status"], self._env, self._project)
self.assertFalse(os.path.exists(os.path.join(pkg, "PWNED")))


class TestRunnerBundledMatchesShared(unittest.TestCase):
"""The bundled copy must stay in sync with the shared runner."""

Expand Down
Loading