豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit 26c4e07

Browse files
author
SHAdd0WTAka
committed
security(workflows): fix script injection vulnerabilities (CodeQL alerts)
Fixes multiple 'Dangerous-Workflow' and 'Clear-text logging' alerts: 1. mcp/obsidian/server.py: - Add mask_sensitive_data() helper - Log masked versions of secrets to stderr - Prevents clear-text logging of sensitive data 2. .github/workflows/zenclaw-discord.yml: - Move all github.event.* variables to env section - Prevents shell injection through commit messages, issue titles, etc. - Add security comment explaining the pattern 3. .github/workflows/telegram-notifications.yml: - Move all event data to job-level env variables - Fixes injection in workflow names, issue titles, PR titles 4. .github/workflows/dependabot-auto-merge.yml: - Move PR_TITLE to env variable - Prevents injection through Renovate PR titles Security: All github.event context values are now properly escaped by GitHub Actions before being used in shell commands. See GitHub Security hardening guide: https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions
1 parent f74a82e commit 26c4e07

File tree

4 files changed

+85
-41
lines changed

4 files changed

+85
-41
lines changed

.github/workflows/dependabot-auto-merge.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,11 @@ jobs:
6262
- name: Parse Renovate metadata
6363
if: steps.check.outputs.is-renovate == 'true'
6464
id: renovate-metadata
65+
# SECURITY: PR title passed via env to prevent script injection
66+
env:
67+
PR_TITLE: ${{ github.event.pull_request.title }}
6568
run: |
6669
# Extract metadata from PR title for Renovate
67-
PR_TITLE="${{ github.event.pull_request.title }}"
68-
6970
# Try to determine update type from title
7071
if echo "$PR_TITLE" | grep -q "update.*dependency"; then
7172
# Check for major/minor/patch in title

.github/workflows/telegram-notifications.yml

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -36,63 +36,71 @@ jobs:
3636
env:
3737
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
3838
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
39+
# SECURITY: All event data passed via env to prevent script injection
40+
EVENT_NAME: ${{ github.event_name }}
41+
# Workflow run event
42+
WORKFLOW_NAME: ${{ github.event.workflow_run.name }}
43+
WORKFLOW_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
44+
WORKFLOW_BRANCH: ${{ github.event.workflow_run.head_branch }}
45+
WORKFLOW_URL: ${{ github.event.workflow_run.html_url }}
46+
# Issues event
47+
ISSUE_TITLE: ${{ github.event.issue.title }}
48+
ISSUE_URL: ${{ github.event.issue.html_url }}
49+
ISSUE_ACTION: ${{ github.event.action }}
50+
# PR event
51+
PR_TITLE: ${{ github.event.pull_request.title }}
52+
PR_URL: ${{ github.event.pull_request.html_url }}
53+
PR_ACTION: ${{ github.event.action }}
54+
# Workflow dispatch
55+
INPUT_MESSAGE: ${{ github.event.inputs.message }}
56+
# Common
57+
REPO: ${{ github.repository }}
58+
ACTOR: ${{ github.actor }}
59+
REF_NAME: ${{ github.ref_name }}
3960

4061
steps:
4162
- name: Send Telegram Notification
4263
if: ${{ env.TELEGRAM_BOT_TOKEN != '' && env.TELEGRAM_CHAT_ID != '' }}
4364
run: |
4465
# Determine notification type and message
45-
if [ "${{ github.event_name }}" == "workflow_run" ]; then
46-
WORKFLOW_NAME="${{ github.event.workflow_run.name }}"
47-
CONCLUSION="${{ github.event.workflow_run.conclusion }}"
48-
BRANCH="${{ github.event.workflow_run.head_branch }}"
49-
URL="${{ github.event.workflow_run.html_url }}"
50-
51-
if [ "$CONCLUSION" == "success" ]; then
66+
if [ "$EVENT_NAME" == "workflow_run" ]; then
67+
if [ "$WORKFLOW_CONCLUSION" == "success" ]; then
5268
STATUS="✅ SUCCESS"
53-
elif [ "$CONCLUSION" == "failure" ]; then
69+
elif [ "$WORKFLOW_CONCLUSION" == "failure" ]; then
5470
STATUS="❌ FAILED"
5571
else
56-
STATUS="⚠️ $CONCLUSION"
72+
STATUS="⚠️ $WORKFLOW_CONCLUSION"
5773
fi
5874
59-
MESSAGE="<b>Workflow $STATUS</b> - Name: $WORKFLOW_NAME, Branch: $BRANCH, Repo: ${{ github.repository }} - $URL"
75+
MESSAGE="<b>Workflow $STATUS</b> - Name: $WORKFLOW_NAME, Branch: $WORKFLOW_BRANCH, Repo: $REPO - $WORKFLOW_URL"
6076
61-
elif [ "${{ github.event_name }}" == "issues" ]; then
62-
ISSUE_TITLE="${{ github.event.issue.title }}"
63-
ISSUE_URL="${{ github.event.issue.html_url }}"
64-
ACTION="${{ github.event.action }}"
65-
66-
if [ "$ACTION" == "opened" ]; then
77+
elif [ "$EVENT_NAME" == "issues" ]; then
78+
if [ "$ISSUE_ACTION" == "opened" ]; then
6779
ICON="📋"
68-
elif [ "$ACTION" == "closed" ]; then
80+
elif [ "$ISSUE_ACTION" == "closed" ]; then
6981
ICON="✅"
7082
else
7183
ICON="📝"
7284
fi
7385
74-
MESSAGE="<b>$ICON Issue $ACTION</b> - $ISSUE_TITLE - ${{ github.repository }} - $ISSUE_URL"
86+
MESSAGE="<b>$ICON Issue $ISSUE_ACTION</b> - $ISSUE_TITLE - $REPO - $ISSUE_URL"
7587
76-
elif [ "${{ github.event_name }}" == "pull_request" ]; then
77-
PR_TITLE="${{ github.event.pull_request.title }}"
78-
PR_URL="${{ github.event.pull_request.html_url }}"
79-
ACTION="${{ github.event.action }}"
80-
81-
if [ "$ACTION" == "opened" ]; then
88+
elif [ "$EVENT_NAME" == "pull_request" ]; then
89+
if [ "$PR_ACTION" == "opened" ]; then
8290
ICON="🔀"
83-
elif [ "$ACTION" == "closed" ]; then
91+
elif [ "$PR_ACTION" == "closed" ]; then
8492
ICON="✅"
8593
else
8694
ICON="📝"
8795
fi
8896
89-
MESSAGE="<b>$ICON PR $ACTION</b> - $PR_TITLE - ${{ github.repository }} - $PR_URL"
97+
MESSAGE="<b>$ICON PR $PR_ACTION</b> - $PR_TITLE - $REPO - $PR_URL"
9098
91-
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
92-
MESSAGE="<b>🧪 Test Notification</b> - ${{ github.event.inputs.message }} - ${{ github.repository }} by ${{ github.actor }}"
99+
elif [ "$EVENT_NAME" == "workflow_dispatch" ]; then
100+
MESSAGE="<b>🧪 Test Notification</b> - $INPUT_MESSAGE - $REPO by $ACTOR"
93101
94102
else
95-
MESSAGE="<b>🔔 Repository Event</b> - ${{ github.event_name }} - ${{ github.repository }} - ${{ github.ref_name }} by ${{ github.actor }}"
103+
MESSAGE="<b>🔔 Repository Event</b> - $EVENT_NAME - $REPO - $REF_NAME by $ACTOR"
96104
fi
97105
98106
# Send Telegram message

.github/workflows/zenclaw-discord.yml

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ jobs:
4242

4343
- name: Check Discord Webhook Secret
4444
id: check-secret
45+
env:
46+
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }}
4547
run: |
46-
if [ -n "${{ secrets.DISCORD_WEBHOOK_URL }}" ]; then
48+
if [ -n "$DISCORD_WEBHOOK" ]; then
4749
echo "has_secret=true" >> $GITHUB_OUTPUT
4850
echo "✅ Discord webhook secret is configured"
4951
else
@@ -60,8 +62,22 @@ jobs:
6062
- name: Prepare Notification
6163
id: prep
6264
if: steps.check-secret.outputs.has_secret == 'true'
65+
# SECURITY: All github.event values are passed via env to prevent script injection
66+
# See: https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
67+
env:
68+
EVENT_NAME: ${{ github.event_name }}
69+
# Push event
70+
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
71+
# Issues event
72+
ISSUE_ACTION: ${{ github.event.action }}
73+
ISSUE_TITLE: ${{ github.event.issue.title }}
74+
# Workflow run event
75+
WORKFLOW_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
76+
WORKFLOW_NAME: ${{ github.event.workflow_run.name }}
77+
# Workflow dispatch event
78+
INPUT_MESSAGE: ${{ github.event.inputs.message }}
6379
run: |
64-
EVENT="${{ github.event_name }}"
80+
EVENT="$EVENT_NAME"
6581
COLOR="3447003"
6682
TITLE="ZenClaw Alert"
6783
DESCRIPTION=""
@@ -70,32 +86,32 @@ jobs:
7086
push)
7187
COLOR="3066993"
7288
TITLE="🚀 Push to main"
73-
DESCRIPTION="${{ github.event.head_commit.message }}"
89+
DESCRIPTION="$COMMIT_MESSAGE"
7490
;;
7591
issues)
76-
if [ "${{ github.event.action }}" == "opened" ]; then
92+
if [ "$ISSUE_ACTION" == "opened" ]; then
7793
COLOR="15158332"
7894
TITLE="🐛 New Issue"
7995
else
8096
COLOR="3066993"
8197
TITLE="✅ Issue Resolved"
8298
fi
83-
DESCRIPTION="${{ github.event.issue.title }}"
99+
DESCRIPTION="$ISSUE_TITLE"
84100
;;
85101
workflow_run)
86-
if [ "${{ github.event.workflow_run.conclusion }}" == "success" ]; then
102+
if [ "$WORKFLOW_CONCLUSION" == "success" ]; then
87103
COLOR="3066993"
88104
TITLE="✅ Workflow Success"
89105
else
90106
COLOR="15158332"
91107
TITLE="❌ Workflow Failed"
92108
fi
93-
DESCRIPTION="${{ github.event.workflow_run.name }}"
109+
DESCRIPTION="$WORKFLOW_NAME"
94110
;;
95111
workflow_dispatch)
96112
COLOR="3447003"
97113
TITLE="🧪 Test Message"
98-
DESCRIPTION="${{ github.event.inputs.message }}"
114+
DESCRIPTION="$INPUT_MESSAGE"
99115
;;
100116
esac
101117
@@ -163,10 +179,14 @@ jobs:
163179
164180
- name: Summary
165181
if: always()
182+
env:
183+
PREP_EVENT: ${{ steps.prep.outputs.event }}
184+
HAS_SECRET: ${{ steps.check-secret.outputs.has_secret }}
185+
EVENT_NAME: ${{ github.event_name }}
166186
run: |
167187
echo "## ZenClaw Discord Notification" >> $GITHUB_STEP_SUMMARY
168-
echo "Event: ${{ steps.prep.outputs.event || github.event_name }}" >> $GITHUB_STEP_SUMMARY
169-
if [ "${{ steps.check-secret.outputs.has_secret }}" == "true" ]; then
188+
echo "Event: ${PREP_EVENT:-$EVENT_NAME}" >> $GITHUB_STEP_SUMMARY
189+
if [ "$HAS_SECRET" == "true" ]; then
170190
echo "Status: Webhook configured" >> $GITHUB_STEP_SUMMARY
171191
else
172192
echo "Status: Webhook NOT configured" >> $GITHUB_STEP_SUMMARY

mcp/obsidian/server.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ def list_secrets() -> list:
6868
return secrets
6969

7070

71+
def mask_sensitive_data(value: str) -> str:
72+
"""Mask sensitive data for logging - only show first/last 4 chars"""
73+
if not value or len(value) <= 8:
74+
return "***"
75+
return f"{value[:4]}...{value[-4:]}"
76+
77+
7178
def main():
7279
"""MCP Server main loop"""
7380
print("Obsidian MCP Server started", file=os.sys.stderr)
@@ -83,7 +90,13 @@ def main():
8390
name = params.get("name")
8491
value = get_secret(name)
8592
if value:
93+
# Return full value to client, but log masked version
8694
response = {"result": value, "error": None}
95+
# Log to stderr (not stdout which is MCP protocol)
96+
print(
97+
f"Secret '{name}' retrieved (masked: {mask_sensitive_data(value)})",
98+
file=os.sys.stderr
99+
)
87100
else:
88101
msg = f"Secret '{name}' not found"
89102
response = {"result": None, "error": msg}
@@ -106,6 +119,8 @@ def main():
106119
else:
107120
response = {"error": f"Unknown method: {method}"}
108121

122+
# Output JSON response to stdout (MCP protocol)
123+
# Note: Secret values are masked in logs but returned full to client
109124
print(json.dumps(response))
110125

111126
except EOFError:

0 commit comments

Comments
 (0)