You Could Say Where a Skill Landed. Not Where It Came From.
CVE-2026-85685, published 4 September 2026 and tracked in the GitHub Advisory Database as GHSA-gpf9-4466-gc5g, describes AgentScope's workspace add_skill operation through version 2.0.7.post1: the destination a skill is copied to is confined to the skills directory, but the source path a caller supplies is not confined to anything at all. The underlying flaw was reported privately on 12 June 2026 and opened as a public GitHub issue, number 2069, on 13 July 2026. This desk read the exact source at the affected release directly and confirms the mechanism. No maintainer response, no fix and no patched version exist in anything this record could find.
Event analysed: . This analysis was published on 4 September 2026.
No, and CVE-2026-85685 is the precise demonstration. GHSA-gpf9-4466-gc5g, carrying a CVSS 4.0 base score of 8.7, High severity, under CWE-22, describes AgentScope's LocalWorkspace.add_skill method as copying an arbitrary server directory into an agent's workspace because the source path parameter, skill_path, receives no confinement check at all, while the destination directory the copy writes into does. This record read the exact code at the affected release, AgentScope 2.0.7.post1, directly rather than relying on the advisory's own description. The method resolves dest_path inside the agent's skills directory and raises an error if os.path.realpath(dest_path) does not start with os.path.realpath(skills_dir), a real, working check. It then calls shutil.copytree(skill_path, dest_path, dirs_exist_ok=False) with the caller supplied skill_path unchanged: no realpath check, no allowed root, no rejection of an absolute path pointing anywhere the server process can read. The only gate on the source is that it contains a SKILL.md file with name and description frontmatter fields, a content shape check, not a location check, and an attacker supplying skill_path can place that one small file alongside whatever else sits in the same directory. The HTTP endpoint that exposes this, POST /workspace/skill, is marked deprecated in AgentScope's own router but still wires AddSkillRequest.skill_path straight into workspace.add_skill(body.skill_path), guarded only by a get_current_user_id dependency that accepts any non empty X-User-ID header value as the caller's identity, a mechanism its own docstring calls temporary and states will be replaced by JWT authentication. The underlying issue was first reported privately on 12 June 2026 and opened as public GitHub issue 2069 on 13 July 2026, more than two months before the CVE itself published on 4 September 2026, and as of this record neither the issue nor the advisory shows a maintainer response, a merged fix or a patched release naming AgentScope 2.0.7.post1's successor. This record does not claim the vulnerability has been exploited against a real deployment, and does not claim every AgentScope deployment exposes this HTTP endpoint to a network an attacker can reach.
AgentScope's workspace code got one half of this check right. Before LocalWorkspace.add_skill writes a copied skill into an agent's skills directory, it resolves the write target with os.path.realpath and confirms the result still starts with the skills directory's own resolved path, raising a plain ValueError if it does not. That is exactly the shape of check a destination confinement guard should take, and reading the source directly at the affected release shows it running, unconditionally, on every call. The gap sits one argument to the left. The same method takes a skill_path argument naming where the skill is copied from, and nothing in the method, the request schema that carries it, or the endpoint that receives it, subjects that argument to any comparable check.
What add_skill actually does, read directly against the source
AgentScope describes itself, in its own published papers, as a developer centered framework for building agentic applications and a flexible multi agent platform. Inside that framework, an agent's skills are ordinary directories on disk, each expected to carry a SKILL.md file whose frontmatter names the skill and describes it. LocalWorkspace.add_skill exists to copy one such directory into an agent's own skills folder so the agent can use it. This desk cloned agentscope-ai's own repository at the tag matching the CVE's affected range, 2.0.7.post1, and read the method directly rather than working from the advisory's summary of it. The method first normalizes skill_path through a small helper, _normalize_local_path, confirmed by direct reading to do exactly two things: expand a leading user home shorthand and call os.path.abspath, neither of which confines a path to anywhere. It then validates the source by checking for a SKILL.md file at skill_path and parsing its frontmatter for name and description fields, raising ValueError if either is missing. Once that passes, it resolves a conflict free destination name inside the agent's skills directory, checks that resolved destination path against the skills directory with realpath, and only then calls shutil.copytree(skill_path, dest_path, dirs_exist_ok=False), passing the still unconfined skill_path straight in as the function's own source argument.
One check that runs, and one that was never written
Two questions have to be answered before a copy like this is safe, and add_skill only asks one of them. Is the place a file is about to be written into somewhere the agent is meant to write. That question gets a real, working answer: the destination realpath check. Is the place a file is about to be read from somewhere the caller was ever entitled to read. That question gets no answer at all. The SKILL.md check that does run answers a different question entirely, whether the source directory's contents look like a skill, which any attacker who can write one small text file to any readable location can satisfy without needing to be entitled to anything else in that directory. A source that resembles a skill is not the same fact as a source that lies inside an authority a caller actually holds, and add_skill's own code treats the first as though it settled the second.
This is not the shape of a canonicalization bypass, where a check exists and a crafted input defeats it. There is no equivalent check to defeat here. The confinement logic that protects the destination simply was never written a second time for the source, on a code path where the same function needed both. Read plainly, authority over where a copy operation is permitted to write was treated as though it also settled where the same operation may read from, and the two are different resources with different, unrelated authority questions attached to them.
What a caller actually reaches through this
Whatever the process running AgentScope's workspace service can read becomes reachable through this path, subject only to the one content constraint that a SKILL.md file with name and description fields sits inside the chosen directory. An attacker who controls, or can write to, any directory the server can read, a shared filesystem mount, a path an earlier unrelated vulnerability already exposed, or simply a location the deployer never expected an agent operation to touch, can point skill_path at it, add one small file to satisfy the content check, and have shutil.copytree carry the entire directory's contents into the agent's own skills folder. AgentScope's workspace already exposes a skill listing to the agent and, through it, to whatever the agent's own interface surfaces to a user. Nothing about the copy operation marks the imported files as anything other than an ordinary, legitimately installed skill once they land, and nothing this record found describes a review step between the copy completing and the agent treating the imported directory as a normal part of its own skill set.
A separate weakness worth keeping separate: who is asking
AgentScope's app/deps.py defines get_current_user_id, the dependency every workspace route including add_skill relies on to establish who is calling. Read directly, it does one thing: it reads the X-User-ID request header, raises an unauthorized error if the header is empty, and otherwise returns the header's own value as the authenticated user id. Its own docstring states plainly that this is temporary header based identity that will be replaced by JWT authentication. That is a real weakness, and this record states it precisely rather than folding it into the source path failure above: a caller's bare assertion of who it is stands in for anything a system would ordinarily verify before trusting an identity claim. But it is a different gap. The source path failure exists even for a request whose X-User-ID happens to be genuine, because nothing downstream of authentication ever checks whether the authenticated caller was entitled to name the specific source directory it supplied. This record connects the two only as two weaknesses reachable through the same deprecated endpoint, not as one mechanism, and does not claim that fixing either one closes the other.
Chronology: reported in June, opened in July, published in September
The GitHub issue that carries this disclosure, number 2069 and titled to describe an unconfined source path in workspace add_skill copying arbitrary server directories into the agent workspace, states the underlying flaw was originally reported on 12 June 2026 and states the issue itself was opened publicly on 13 July 2026, a full month later. CVE-2026-85685 was not published until 4 September 2026, almost two months after the public issue and nearly three months after the original private report. This record treats 12 June 2026 as the date of the underlying technical discovery and states plainly that the CVE's own publication almost three months later is a formal recognition of an already reported flaw, not the moment the flaw was found. As of this record, the GitHub issue remains open, carries a state label of under discussion, and shows no maintainer reply, no linked pull request and no released version of AgentScope past 2.0.7.post1 that a tag listing on the project's own repository could confirm as carrying a fix.
Where this sits against what this desk has already argued
This desk has argued at length that reachability substitutes for authorization wherever nothing else stands between an actor and a resource it can technically reach, most recently including a Grafana MCP server whose own outbound request target was a caller supplied header rather than a value checked against an allowed destination. add_skill's source path is the same substitution read from the opposite direction of the same operation: instead of a caller directed outbound request reaching wherever a header points, a caller directed copy reaches inward from wherever a parameter points, into a location the agent itself will then treat as trusted. Both are instances of a system asking whether a request is well formed rather than whether the specific value inside it names something the caller was ever entitled to reach.
A patched AshAi vulnerability showed that authorizing an operation on a resource type is not the same decision as authorizing which specific record the operation resolves against, and a patched Omnigent vulnerability showed that authority over a container is not the same decision as authority over the object the container happens to reference. This vulnerability sits beside both without matching either exactly. AshAi's and Omnigent's gaps each concerned one resource identity silently standing in for another inside a single sided operation. add_skill's operation has two sides, a place written to and a place read from, and confining one side correctly did nothing to establish the other: a system can get the resource identity question completely right for a destination and never ask it at all for a source feeding the same call.
Cursor's own advisory for CVE-2026-50549 showed a canonicalization check that ran, failed, and then let a fallback trust the very value the check existed to verify. AgentScope's add_skill shows a plainer variant with no failure step in between: the source path confinement check simply does not exist on this path, so there is nothing to fail and nothing to fall back from. Both leave a caller controlled path reaching outside the boundary a workspace operation is supposed to enforce, but Cursor's flaw needed an attacker to induce a failure; AgentScope's flaw needed nothing more than supplying an ordinary, well formed request.
AgentScope's Authority Provenance ledger
Moona Intelligence applies the same questions to this vulnerability that this desk applies to every mechanism it examines, read here against the exact source at the affected release rather than a secondary account of it.
Authority grantor. Whatever mechanism, if any, decides that a given X-User-ID value is entitled to call the deprecated add_skill endpoint at all. Nothing this record could read establishes that mechanism beyond the header's own presence, since get_current_user_id verifies only that the header is non empty.
Mandate or basis. A caller supplied identity claim and a request naming an agent id, a session id and a skill_path, none of which this record's reading of the source found checked against any resource scope the caller actually holds over the named source directory.
Delegated scope. Intended scope, on the strongest reading available, was presumably a skill source the deploying organization itself controls or approves. Actual scope, on the code as read, is any directory the server process can read and that contains one file satisfying a content shaped check, with no location constraint at all.
Explicit limits. A limit exists, and works, on the other side of the same call: the destination realpath check against the skills directory. No equivalent limit exists on the source side, an absence this record confirmed by reading the method in full rather than inferring it from the check's absence alone.
Inherited permissions or assumptions. This record's own reading, not a stated project rationale: the destination confinement check appears to have been written on the assumption that confining where a copy writes to is the security relevant half of a copy operation, leaving where it reads from unexamined.
Revocation or modification. None found. No patched version, no merged pull request and no maintainer statement addressing this issue appears in the GitHub issue, the advisory, or the project's own tag history through this record's review.
Challenge authority. None. Nothing in the endpoint, the request schema or the workspace method interposes a second reviewer or an approval step between the request and the copy it performs.
Recovery. Not addressed by anything this record could find. The issue's own proposed remediation, confining the source path the same way the destination already is, rejecting absolute paths and traversal sequences, and restricting sources to an approved root, is a proposal from the report, not a shipped fix, and this record does not treat it as one.
Provenance evidence quality. The mechanism is confirmed by this session's own direct read of the source at the exact affected tag, cross checked against the GitHub issue's own account and the GitHub Advisory Database's CVE and CWE metadata, which agree on every material point. This advisory carries no page under the agentscope-ai organization's own security advisories tab, unlike some fixed vulnerabilities this desk has covered elsewhere; it exists only in the community facing GitHub Advisory Database, consistent with an unfixed report rather than a vendor self disclosed and patched one. This session's direct fetch of the official CVE Program record at cve.org was blocked by this session's own network egress policy; the CVE identifier, publication date and CWE classification are instead corroborated through the GitHub Advisory Database's own record, which names the same CVE, and through independent aggregator listings this session could reach, and are stated here as corroborated rather than independently read from the Program's own record.
What this does not establish
This is a reported, unfixed vulnerability, not a confirmed exploitation. This record found no evidence that CVE-2026-85685 has been exploited against a real AgentScope deployment, and does not claim it has. It does not establish what share of AgentScope deployments run the HTTP application layer this vulnerability lives in at all, since AgentScope's own workspace abstraction supports several backends and the vulnerable method is specific to the local filesystem workspace reached through this deprecated endpoint. It does not establish that AgentScope's maintainers have declined to fix the issue, only that no fix, no response and no patched release appear in anything public this record could read as of 4 September 2026. It treats the X-User-ID identity weakness as a separate, connected finding rather than as part of the same mechanism, and it does not claim any agent using AgentScope has autonomously selected or exploited this path; the mechanism this record describes is a server side operation reachable by an ordinary, well formed request, with no autonomous agent behavior involved in triggering it.
Sources
This analysis interprets third-party reporting, research and announcements. Moona is not the original reporter of the underlying events.
Protocol evidence
This record does not assess these architectures. The connection runs through the Risk Registry requirement each one bears on, and these published authority architectures are what the evidence says about that requirement.
Protocol evidence related through AEW-018 Authority over a destination treated as authority over the source that fills it
- Missing requirement
Agent Action Decision Protocol (AADP)
Shamik Saha, individual submission to the IETF
Requirement The draft separates agent identity and standing access from authorization of one concrete action
As described in material supplied to this session, AADP argues that its own decision addresses whether a specific proposed action, with specific argument values, may proceed, distinct from identity and standing access. AgentScope's add_skill vulnerability shows a resource identifying argument, skill_path, that is exactly the kind of specific argument value AADP's own described decision would need to evaluate, and nothing in the material available to this session, for either revision of the draft, states that evaluating a proposed action's argument values includes checking that a resource identifying value resolves inside an authorized location rather than only checking its content or format. This session did not read either revision's own text directly. Recorded as a missing requirement rather than a supported one because the available description states a decision over action and argument values in general terms, not a location confinement requirement for a transfer's source specifically.
This record is the cited evidence for this relationship.
