Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/codeql/queries/python/FilesystemWriteUsage.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @name Plugin shared directory creation capability use
* @description Dispatcharr's watched directory creation helper requires the filesystem_write capability.
* @kind problem
* @problem.severity warning
* @precision medium
* @id plugin/capability-contract/filesystem-write
* @tags security external/cwe/cwe-269
*/

import python

from Call call, Subscript helper, Subscript dispatcharr, StringLiteral helperKey,
StringLiteral dispatcharrKey, Name context
where
call.getFunc() = helper and
helper.getObject() = dispatcharr and
helper.getIndex() = helperKey and
helperKey.getText() = "ensure_directory" and
dispatcharr.getObject() = context and
context.getId() = "context" and
dispatcharr.getIndex() = dispatcharrKey and
dispatcharrKey.getText() = "dispatcharr"
select call, "Creating a watched directory requires the `filesystem_write` capability."
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
| bad.py:1:1:1:51 | Subscript() | Creating a watched directory requires the `filesystem_write` capability. |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FilesystemWriteUsage.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
context["dispatcharr"]["ensure_directory"]("logos")
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
context["dispatcharr"]["remove_directory"](context["data_dir"] + "/snapshot")
context["other"]["ensure_directory"]("logos")
other["dispatcharr"]["ensure_directory"]("logos")
2 changes: 1 addition & 1 deletion .github/pluginctl/src/pluginctl/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"min_manifest_version": 1,
"max_manifest_version": None,
"runtime_enforced": True,
"codeql_rules": [],
"codeql_rules": ["plugin/capability-contract/filesystem-write"],
},
"celery_dispatch": {
"group": "system",
Expand Down
1 change: 1 addition & 0 deletions .github/pluginctl/tests/test_capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def test_each_runtime_enforced_static_rule_maps_to_its_capability():
"network_listener": "plugin/capability-contract/network-listener",
"subprocess": "plugin/capability-contract/subprocess",
"outbound_network": "plugin/capability-contract/outbound-network",
"filesystem_write": "plugin/capability-contract/filesystem-write",
"celery_dispatch": "plugin/capability-contract/celery-dispatch",
}
for capability_id, rule_id in expected.items():
Expand Down
19 changes: 19 additions & 0 deletions .github/pluginctl/tests/test_sarif.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,25 @@ def test_sandbox_bypass_rules_respect_schema_ranges(tmp_path, monkeypatch):
assert not sarif.sandbox_result_requires_review(result(retired_rule))


def test_filesystem_write_contract_respects_manifest_and_capability(tmp_path, monkeypatch):
plugin_dir = tmp_path / "plugins" / "demo"
plugin_dir.mkdir(parents=True)
monkeypatch.chdir(tmp_path)
result = {
"ruleId": "plugin/capability-contract/filesystem-write",
"locations": [{"physicalLocation": {"artifactLocation": {"uri": "plugins/demo/plugin.py"},
"region": {"startLine": 1}}}],
}
for version, declared, requires_review in (
(0, [], False), (1, [], False), (2, [], True),
(2, ["filesystem_write"], False),
):
(plugin_dir / "plugin.json").write_text(json.dumps({
"manifest_version": version, "capabilities": declared,
}), encoding="utf-8")
assert sarif.contract_result_requires_review(result) == requires_review


def test_capability_contract_accepts_declared_or_legacy_manifests(tmp_path, monkeypatch):
plugin_dir = tmp_path / "plugins" / "demo"
plugin_dir.mkdir(parents=True)
Expand Down
Loading