The previous __-joined scheme was not injective: a directory named
a__b and a nested path a/b both mapped to the same ID. Use a sha256
hash suffix of the resolved path instead, which is collision-free
and keeps the dir name as a human-readable prefix.
Added regression test for separator collision case.
workflow_dir.name was used as the discovery key and API ID, so two
different WORKFLOW_ROOTS containing a leaf directory with the same
name would silently drop one. Use root name + relative path joined
with __ as a stable unique ID instead.
Co-authored-by: wul48527-code <wul48527-code@users.noreply.github.com>
Without this, the no-skill path ignores the resolved max_iterations
and uses whatever default the agent has, instead of the configured
grounding_max_iterations value.
Co-authored-by: wul48527-code <wul48527-code@users.noreply.github.com>
- Add resolve() + is_relative_to() check in _extract_zip() to block
nested traversal entries like nested/../../escape.txt
- Sanitize server-provided skill name in import_skill() to prevent
directory escape via malicious record metadata
- Add 6 regression tests covering both attack vectors
Closes#17
Co-authored-by: LeftX <xzq-xu@users.noreply.github.com>
Versions 1.82.7 and 1.82.8 of litellm were published on March 24, 2026
and contained malicious code that exfiltrated credentials (SSH keys,
cloud credentials, .env files, API keys) to an attacker-controlled domain.
Pin the dependency to >=1.70.0,<1.82.7 in both pyproject.toml and
requirements.txt as a stopgap until litellm can be replaced with direct
provider SDK calls.
See: https://github.com/HKUDS/OpenSpace/issues/31
Ref: PYSEC-2026-2, BerriAI/litellm#24521
Fixes#29. When a skill is already registered, register_skill_dir()
returned None, which caused fix_skill() to incorrectly report a failure.
Now returns the existing SkillMeta instead of None when the skill_id
is already present in the registry, making register_skill_dir() truly
idempotent as its callers (fix_skill, _auto_register_skill_dirs) expect.
Per reviewer feedback: keep strict \byes\b / \bno\b word boundaries to
prevent false positives, but widen confirm/reject/skip to stem-style
\bconfirm\w*\b etc. so common LLM variants like "confirmed", "rejected",
"skipping" still parse correctly instead of falling through to the
default False path.