fix: remove custom command from setAlarm to prevent command injection
This commit is contained in:
@@ -78,7 +78,6 @@ query {
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Set an alarm:**
|
**Set an alarm:**
|
||||||
*(Note: `command` is optional. If omitted, the API will automatically figure out the correct command to start `wecker.py`)*
|
|
||||||
```graphql
|
```graphql
|
||||||
mutation {
|
mutation {
|
||||||
setAlarm(
|
setAlarm(
|
||||||
@@ -91,6 +90,7 @@ mutation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
*(Note: The `command` is managed automatically by the API and cannot be overridden, to prevent command injection into the system crontab.)*
|
||||||
|
|
||||||
**Delete an alarm:**
|
**Delete an alarm:**
|
||||||
```graphql
|
```graphql
|
||||||
|
|||||||
@@ -70,11 +70,9 @@ class Mutation:
|
|||||||
def set_alarm(
|
def set_alarm(
|
||||||
self,
|
self,
|
||||||
cron_expression: str,
|
cron_expression: str,
|
||||||
command: Optional[str] = None,
|
|
||||||
is_enabled: bool = True,
|
is_enabled: bool = True,
|
||||||
id: Optional[str] = None,
|
id: Optional[str] = None,
|
||||||
) -> Alarm:
|
) -> Alarm:
|
||||||
if command is None:
|
|
||||||
command = _default_command()
|
command = _default_command()
|
||||||
|
|
||||||
manager = get_manager()
|
manager = get_manager()
|
||||||
|
|||||||
+19
-3
@@ -121,6 +121,22 @@ def test_set_alarm_default_command_append():
|
|||||||
client.post("/graphql", json={"query": mutation_delete}, headers=headers)
|
client.post("/graphql", json={"query": mutation_delete}, headers=headers)
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_alarm_rejects_custom_command():
|
||||||
|
"""Custom command argument must be rejected to prevent crontab injection."""
|
||||||
|
headers = {"X-API-Key": "test-secret"}
|
||||||
|
mutation = """
|
||||||
|
mutation {
|
||||||
|
setAlarm(cronExpression: "0 9 * * *", command: "rm -rf /") {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
res = client.post("/graphql", json={"query": mutation}, headers=headers)
|
||||||
|
assert res.status_code == 200
|
||||||
|
assert "errors" in res.json()
|
||||||
|
assert "command" in str(res.json()["errors"])
|
||||||
|
|
||||||
|
|
||||||
def test_graphql_workflow():
|
def test_graphql_workflow():
|
||||||
headers = {"X-API-Key": "test-secret"}
|
headers = {"X-API-Key": "test-secret"}
|
||||||
|
|
||||||
@@ -139,7 +155,7 @@ def test_graphql_workflow():
|
|||||||
# 2. Set alarm
|
# 2. Set alarm
|
||||||
mutation_set = """
|
mutation_set = """
|
||||||
mutation {
|
mutation {
|
||||||
setAlarm(cronExpression: "30 7 * * *", command: "python wecker.py", isEnabled: true) {
|
setAlarm(cronExpression: "30 7 * * *", isEnabled: true) {
|
||||||
id
|
id
|
||||||
cronExpression
|
cronExpression
|
||||||
command
|
command
|
||||||
@@ -151,7 +167,7 @@ def test_graphql_workflow():
|
|||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
alarm = res.json()["data"]["setAlarm"]
|
alarm = res.json()["data"]["setAlarm"]
|
||||||
assert alarm["cronExpression"] == "30 7 * * *"
|
assert alarm["cronExpression"] == "30 7 * * *"
|
||||||
assert alarm["command"] == "python wecker.py"
|
assert "wecker.py" in alarm["command"]
|
||||||
assert alarm["isEnabled"] is True
|
assert alarm["isEnabled"] is True
|
||||||
alarm_id = alarm["id"]
|
alarm_id = alarm["id"]
|
||||||
|
|
||||||
@@ -176,7 +192,7 @@ def test_graphql_workflow():
|
|||||||
# 5. Update alarm
|
# 5. Update alarm
|
||||||
mutation_update = f"""
|
mutation_update = f"""
|
||||||
mutation {{
|
mutation {{
|
||||||
setAlarm(id: "{alarm_id}", cronExpression: "0 8 * * *", command: "python wecker.py", isEnabled: false) {{
|
setAlarm(id: "{alarm_id}", cronExpression: "0 8 * * *", isEnabled: false) {{
|
||||||
id
|
id
|
||||||
cronExpression
|
cronExpression
|
||||||
isEnabled
|
isEnabled
|
||||||
|
|||||||
Reference in New Issue
Block a user