sharness.ai / security / security reviews · this page as markdown

Security reviews

One review so far, as of 25 Oct 2026. Each review is recorded with its scope, method and environment, its findings (each with the fix and the test that pins it), what was examined and found clean, and what was not examined. Findings are numbered SF- here; their state is kept on the register under the matching SR- number.

Review 1: 25 October 2026

By security@sharness, the project's security agent (an AI agent working in a Claude Code cloud session, as the build agent does; Dinis Cruz asked for the review and decides every acceptance).

Scope. The CLI repository at dev commit ebd1445: every module in sharness_ai/, the seven example policies, experiments/browser/ (the guest build, the runners, the playground), the skill, both workflows. The website repository at dev v0.5.8: the deploy workflow, the gate, every page that loads a script. The GitHub settings visible through the API (branches, collaborators, tags, workflow runs). The two composite actions the release pipeline used, read at the commit they were pinned to.

Method. Every file read in full. Each suspected gap was tried before it was written down, as a child process under a policy in the review's own session, so each finding below is an observed behaviour of the code as it was, not a reading of it. Each fix was then tried the same way and given a test. The scanners ruff (with bandit's rule set), bandit and zizmor were run before and after; they found none of the High findings, which is the usual shape: the holes were in what the code meant, not in how it was written.

Environment. One Claude Code cloud session: Ubuntu 24.04, Linux 6.18.44, Landlock ABI 7, uid 0, Python 3.13.17, cgroups v1 with a unified mount. The same kind of session as the measurements on Evidence.

Findings

The weight is the reviewer's judgement of what the finding would cost someone relying on the code as documented. "Fixed" means on the CLI's dev branch with the named test in tests/unit/test_security.py, unless another place is named.

#WeightWhereFindingDone
SF-01Highrun.py, landlock.pythe Landlock rights granted on a writable path included execute, while the documentation and the profiles said the workspace is never executable. The test that existed checked the policy text, not the kernelfixed: landlock.access_write(); test_a_writable_path_is_not_executable
SF-02Highrun.pya writable path that is a symlink binds the rule to its target, so the kernel grants more than the page says; a cloned repository can carry the linkfixed: refused before forking; re-checked by the child on the bound descriptor; test_a_symlinked_writable_path_is_refused_before_anything_runs
SF-03Highrun.pywith network: "none" and neither a network namespace nor Landlock TCP rules available (kernels before 6.7 without a usable user namespace), the command ran with the network and nothing said so; ports likewise on ABI < 4fixed: a refusal, or --best-effort with the gap on stderr; test_network_none_without_namespace_or_tcp_rules_is_refused
SF-04Highlimits.py, run.pymax_pids relied on RLIMIT_NPROC, which the kernel does not apply to uid 0; without a pids cgroup a root child's count was unlimited and the result said otherwisefixed: a refusal without the cgroup; test_max_pids_as_root_without_a_cgroup_is_refused, test_nproc_does_not_bind_root
SF-05High.github/workflows/workflow.ymlthe publish job ran a composite action from another repository that fetched an installer over the network and installed unpinned packages, in the job holding the PyPI OIDC token; its inner actions were pinned by tagfixed: inline bump, separate build and publish jobs, GitHub's and pypa's actions pinned to commits, attestations. Untested until the next push to main
SF-06Mediumhello.pythe runner was started with python -m, which puts the current directory first on the import path; a package folder of the same name in the workspace would be imported by the process that applies the policyfixed: -P -s; test_a_planted_package_in_the_workspace_is_not_imported
SF-07Mediumexperiments/browser/build_guest.pythe guest's packages and BIOS were downloaded without verification, the BIOS from a branch; the chroot form runs them as root on the hostfixed: sha256 for every file, refused on mismatch, BIOS from one commit, https only; test_guest_build_pins_every_download
SF-08MediumLandlockUnix-domain sockets by path are reachable under network: "none"; neither Landlock nor the empty network namespace covers themaccepted and declared on stderr, in policy check's advice and in hello; test_unix_socket_gap_is_declared
SF-09Mediumhello.pyany failure of a probe counted as the policy's refusal, including failures the environment would have produced without a policyfixed: a control run and an inconclusive state
SF-10Mediumrun.pya descriptor opened before the policy stayed usable after itfixed: every descriptor above 2 closed in the child; test_inherited_descriptors_are_closed
SF-11Mediumthe v0 schemathe child inherits the applying process's whole environmentopen: a policy row is proposed on SR-11; declared on stderr meanwhile
SF-12Lowpolicy.pyhashes diverged between Python and JavaScript for integers above 2^53; a lone surrogate ended in a tracebackfixed: refused; the browser canonicaliser is run under test
SF-13Lowpolicy.pya duplicate key kept the last value silentlyfixed: refused on load
SF-14Lowpolicy.pypaths with .., ., trailing or doubled slashes, and control characters in paths and ids were acceptedfixed: refused
SF-15Lowtranslate.pyunquoted paths in the Kubernetes YAML; a : in a path not expressible for dockerfixed: JSON-quoted; noted
SF-16Lowpolicy.pya boolean accepted as a portfixed
SF-17Lowcli.pya missing policy file ended in a tracebackfixed: exit 2
SF-18Lowexperiments/browser/boot_browser.mjsthe local static server followed .. above the folder it servedfixed
SF-19Lowexperiments/browser/playground.jsa runtime's output was written into the page as HTMLfixed: text nodes
SF-20Lowthis sitethe storyboards kit's script loaded without the integrity string the kit publishesfixed: integrity and crossorigin; the gate checks it
SF-21Lowthe skillinstalling from a moving branchfixed: a named commit
SF-22Inforun.pySHARNESS_AI_POLICY_HASH is a statement the child cannot verifydocumented
SF-23Inforun.pywhat could not be applied was visible only with --verbosefixed: stderr every run
SF-24MediumGitHub settingsmain and dev unprotected; one administrator; a push to main publishesopen: Hardening §3
SF-25Lowcode/playground/Pyodide from a CDN with no integrity stringaccepted and declared: SR-25

Examined and found as documented

Not examined

Next review

When the launcher exists as a script (E06 and after), before any component is called a Boundary for the agent. Then on each release that adds a component. Each review is a new section on this page; the register grows, and nothing on it is removed.