docs(client): Agent Skills — close out the security review's SDK-side remainder - #66
Open
XieX wants to merge 1 commit into
Open
docs(client): Agent Skills — close out the security review's SDK-side remainder#66XieX wants to merge 1 commit into
XieX wants to merge 1 commit into
Conversation
… remainder The four items left open in the response to the Agent Skills security design review. Three are documentation, one is tests; no behavior changes, and the code halves of rows 9 and 2 are deliberately untouched. **Privilege separation** (row 9 docs half, and the agreed counter-proposal for row 26). The recommended deployment runs the reconcile as a different identity than the agent, which is the whole reason the ``0644``/``0755`` modes deny anything: the agent reads its instructions and cannot rewrite them, or the manifest. That is the mitigation for AZ-1, a prompt-injected agent editing its own skills. Write access to the manifest is the worse half — it is what tells the *next* reconcile which paths the SDK may delete — which is why ``_prune`` re-validates every entry from scratch rather than trusting it. The README documents the pattern and hands the operator the check to run, because the SDK cannot run it: it knows only its own identity, which trivially has write access, having just written there. So ``ReconcileReport`` grows no writability field — the review asked for one and we declined, since any check the SDK could make would answer a different question than the one asked and manufacture false confidence exactly where caution is wanted. ``agents.md`` records that reasoning so the field is not added later by someone reading its absence as an oversight. **Three hostile-manifest prune tests** (row 12 remainder): a well-formed manifest listing ``/etc/passwd``, ``../../../etc/passwd``, and a path under a parent that has since become a symlink. ``_prune`` already refuses all three, so these turn asserted into verified. Two things make them worth more than their line count. They are deliberately *well-formed* — the corrupt-manifest suite above them proves nothing here, because a corrupt manifest suppresses every destructive action wholesale, whereas these manifests give the implementation everything it needs to prune. And "deleted nothing" is asserted through an unlink spy rather than by checking that ``/etc/passwd`` still exists: the test process cannot delete that file anyway, so the obvious assertion would pass against an implementation with no path check at all. **One sentence on** ``"*"`` (row 16 remainder). It materializes the whole project library, so every skill's ``description`` enters the agent's context — including skills no AI Config references and skills belonging to other teams. **The Windows platform bound is now explicit** (row 2 residual), in ``safe_fs.py``, ``agents.md`` and the README. Reparse-point checks (``GetFileAttributesW`` / ``FILE_FLAG_OPEN_REPARSE_POINT``) are not implemented, by decision: Windows is not a supported or tested platform for this release, neither repository has a Windows CI runner so the checks would ship unverified, and the TypeScript SDK could not match them in any case because Node exposes no ``*at()`` family on *any* platform. Implementing them in Python alone would break cross-language parity and trade a documented bound for an unverified one. Two consequences are recorded rather than left to be rediscovered: on Windows, write permission on the managed root is the only boundary, which is what makes privilege separation the mitigation and not merely advice; and this retroactively lowers the priority of the row 25 reserved-device-name work, noted where that code lives. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #58. Closes the four items left open in our response to the Agent Skills security design review.
Three documentation items and one test item. No behavior changes — the code halves of rows 9 and 2 were already done and are deliberately untouched; the only change to
safe_fs.pyis its module docstring.1. Privilege separation (row 9 docs half + counter-proposal for row 26)
The proposed mitigation for AZ-1, the review's most serious finding.
The README now documents reconcile-identity ≠ agent-identity as the recommended deployment, which is the whole reason the
0644/0755modes deny anything: the agent reads its instructions and cannot rewrite them, or the manifest. Write access to the manifest is the worse half — it is what tells the next reconcile which paths the SDK may delete — which is why_prunere-validates every entry from scratch rather than trusting it.The operator gets an explicit check to run, because the SDK cannot run it.
ReconcileReportdeliberately grows no writability field. Row 26 asked for one; we declined. The SDK knows only its own identity, which trivially has write access, having just written there. Any check it could make would answer a different question than the one asked and manufacture false confidence exactly where the review wants caution.agents.mdrecords that reasoning so the field is not added later by someone reading its absence as an oversight.2. Three hostile-manifest prune tests (row 12 remainder)
A well-formed manifest listing
/etc/passwd,../../../etc/passwd, and a path under a parent that has since become a symlink._prunealready refuses all three; these turn asserted into verified.Two things make them worth more than their line count:
/etc/passwdstill exists. The test process cannot delete that file anyway, so the obvious assertion would pass against an implementation with no path check at all — permissions would be doing the work. The spy proves the removal is never attempted.3. One sentence on
"*"(row 16 remainder)It materializes the whole project library, so every skill's
descriptionenters the agent's context — including skills no AI Config references and skills belonging to other teams.4. Windows platform bound (row 2 residual) — deferred, explicitly
Decision: defer, with the bound written down. Reparse-point checks (
GetFileAttributesW/FILE_FLAG_OPEN_REPARSE_POINT) are not implemented, and that is now recorded insafe_fs.py,agents.mdand the README rather than left implied.Rationale:
ubuntu-latest— so the checks would ship unverified. Both codebases already cite that absence as a reason not to addos.name/process.platformbranches.*at()family on any platform, so its racylstatfloor is universal rather than Windows-only. Hardening Python alone would break the cross-language parity the two SDKs are held to.Two consequences are recorded rather than left to be rediscovered: on Windows write permission on the managed root is the only boundary, which is what makes privilege separation the mitigation and not merely advice; and this retroactively lowers the priority of the row 25 reserved-device-name work, noted where that code lives. That code stays — it keeps a root written on Linux usable when read from Windows — but it is not evidence that Windows is hardened.
Parity
The Node half is launchdarkly/js-ai-sdk#48, case for case. No customer-visible surface moved: the
ld.skills.integrity_failureevent name, the eightreason_codetokens, the fiveSkillOutcomeReasontokens, and the log-record serialization are untouched.Verification
uv run pytest— 1484 passed, 11 skippeduv run ruff check ./ruff format --check .— cleanuv run mypy packages/*/src(the CI gate) — no issues in 43 source files🤖 Generated with Claude Code
Note
Overview
Closes the remaining Agent Skills security review items with documentation and tests only — no runtime behavior changes beyond an expanded
safe_fs.pymodule docstring.The README and
agents.mdnow spell out deployment and platform limits: runwrite_skillsunder a different identity than the agent (with a shell check for agent writability), decline aReconcileReportroot-writability field, warn that"*"materializes the full skill library into context, and document the POSIX-only descriptor-pinned guarantee versus Windows’s racylstatfloor (reparse-point hardening explicitly deferred).test_skills_fs.pyaddsTestHostileManifestPrune: well-formed manifests that would prune/etc/passwd, traversal paths, or paths under a symlink-swapped parent must error withoutos.unlinkbeing attempted (unlink spy), matching existing_prunedefenses.Reviewed by Cursor Bugbot for commit 6a0e6aa. Bugbot is set up for automated code reviews on this repo. Configure here.