Skip to content

Commit b48032d

Browse files
feat: add organization membership security gate for Claude workflow (#419)
- Create reusable org-membership-check action to verify dotCMS membership - Update Claude workflow with security gate that blocks non-members - Hardcode organization to prevent override attacks - Add comprehensive logging for security decisions Addresses security concern from issue dotCMS#33050 where external users could trigger Claude workflows by mentioning @claude in comments. 🤖 Generated with [Claude Code](https://claude.ai/code) ### Proposed Changes * change 1 * change 2 ### Checklist - [ ] Tests - [ ] Translations - [ ] Security Implications Contemplated (add notes if applicable) ### Additional Info ** any additional useful context or info ** ### Screenshots Original | Updated :-------------------------:|:-------------------------: ** original screenshot ** | ** updated screenshot ** Co-authored-by: Claude <[email protected]>
1 parent bca65e0 commit b48032d

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Organization Membership Check Action
2+
3+
This composite action checks if a GitHub user is a member of the dotCMS organization. It's used as a security gate to ensure only dotCMS organization members can trigger sensitive workflows like Claude code reviews.
4+
5+
## Security Features
6+
7+
- **Hardcoded Organization**: The organization name "dotCMS" is hardcoded and cannot be overridden
8+
- **No Information Leakage**: Does not distinguish between public/private membership in outputs
9+
- **Graceful Error Handling**: Returns clear status without exposing internal API details
10+
11+
## Inputs
12+
13+
| Input | Description | Required | Default |
14+
|-------|-------------|----------|---------|
15+
| `username` | GitHub username to check | Yes | N/A |
16+
17+
## Outputs
18+
19+
| Output | Description | Possible Values |
20+
|--------|-------------|-----------------|
21+
| `is_member` | Boolean indicating membership | `true` or `false` |
22+
| `membership_status` | Detailed status | `member`, `non-member`, or `error` |
23+
24+
## Usage
25+
26+
```yaml
27+
- name: Check organization membership
28+
id: membership-check
29+
uses: ./.github/actions/security/org-membership-check
30+
with:
31+
username: ${{ github.actor }}
32+
33+
- name: Conditional step based on membership
34+
if: steps.membership-check.outputs.is_member == 'true'
35+
run: echo "User is authorized"
36+
```
37+
38+
## Implementation Details
39+
40+
The action uses the GitHub CLI (`gh`) with the repository's `GITHUB_TOKEN` to check organization membership. It first attempts to check public membership, and if that fails, it attempts to check private membership (which requires appropriate permissions in organization repositories).
41+
42+
## Security Considerations
43+
44+
- Only checks membership in the dotCMS organization (hardcoded)
45+
- Does not expose whether membership is public or private
46+
- Logs authorization results without sensitive details
47+
- Uses repository's built-in `GITHUB_TOKEN` for API access
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: 'Organization Membership Check'
2+
description: 'Checks if a user is a member of the dotCMS organization'
3+
4+
inputs:
5+
username:
6+
description: 'GitHub username to check'
7+
required: true
8+
9+
outputs:
10+
is_member:
11+
description: 'true if user is a member, false otherwise'
12+
value: ${{ steps.check-membership.outputs.is_member }}
13+
membership_status:
14+
description: 'Membership status (member, non-member, error)'
15+
value: ${{ steps.check-membership.outputs.membership_status }}
16+
17+
runs:
18+
using: 'composite'
19+
steps:
20+
- name: Check Organization Membership
21+
id: check-membership
22+
shell: bash
23+
run: |
24+
echo "Checking organization membership for user: ${{ inputs.username }} in dotCMS organization"
25+
26+
# Use GitHub CLI to check organization membership
27+
# This uses the GITHUB_TOKEN which has read access to organization membership
28+
29+
set +e # Don't exit on error, we want to handle it gracefully
30+
31+
# Check public membership first
32+
gh api orgs/dotCMS/members/${{ inputs.username }} \
33+
--jq '.login' 2>/dev/null
34+
35+
exit_code=$?
36+
37+
if [ $exit_code -eq 0 ]; then
38+
echo "✅ User ${{ inputs.username }} is a member of dotCMS"
39+
echo "is_member=true" >> $GITHUB_OUTPUT
40+
echo "membership_status=member" >> $GITHUB_OUTPUT
41+
else
42+
# If public membership check fails, try to check private membership
43+
# This requires higher permissions but will work in organization repos
44+
gh api orgs/dotCMS/members/${{ inputs.username }} \
45+
--method GET \
46+
--header "Accept: application/vnd.github.v3+json" 2>/dev/null
47+
48+
private_exit_code=$?
49+
50+
if [ $private_exit_code -eq 0 ]; then
51+
echo "✅ User ${{ inputs.username }} is a member of dotCMS"
52+
echo "is_member=true" >> $GITHUB_OUTPUT
53+
echo "membership_status=member" >> $GITHUB_OUTPUT
54+
else
55+
echo "❌ User ${{ inputs.username }} is not a member of dotCMS"
56+
echo "is_member=false" >> $GITHUB_OUTPUT
57+
echo "membership_status=non-member" >> $GITHUB_OUTPUT
58+
fi
59+
fi
60+
61+
# Log the result for debugging (without leaking membership details)
62+
membership_result=$(if [ "$(cat $GITHUB_OUTPUT | grep 'is_member=true')" ]; then echo "AUTHORIZED"; else echo "UNAUTHORIZED"; fi)
63+
echo "::notice::Organization membership check result: $membership_result for ${{ inputs.username }}"
64+
env:
65+
GITHUB_TOKEN: ${{ github.token }}

.github/workflows/issue_comment_claude-code-review.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,34 @@ on:
1919
types: [created]
2020

2121
jobs:
22+
# Security gate: Check if user is dotCMS organization member
23+
security-check:
24+
runs-on: ubuntu-latest
25+
outputs:
26+
authorized: ${{ steps.membership-check.outputs.is_member }}
27+
steps:
28+
- name: Checkout repository
29+
uses: actions/checkout@v4
30+
31+
- name: Check organization membership
32+
id: membership-check
33+
uses: ./.github/actions/security/org-membership-check
34+
with:
35+
username: ${{ github.event.comment.user.login || github.actor }}
36+
37+
- name: Log security decision
38+
run: |
39+
if [ "${{ steps.membership-check.outputs.is_member }}" = "true" ]; then
40+
echo "✅ Access granted: User is a dotCMS organization member"
41+
else
42+
echo "❌ Access denied: User is not a dotCMS organization member"
43+
echo "::warning::Unauthorized user attempted to trigger Claude workflow: ${{ github.event.comment.user.login || github.actor }}"
44+
fi
45+
2246
# Interactive Claude mentions (simplified using centralized logic)
2347
claude-interactive:
48+
needs: security-check
49+
if: needs.security-check.outputs.authorized == 'true'
2450
uses: dotCMS/ai-workflows/.github/workflows/[email protected]
2551
with:
2652
trigger_mode: interactive

0 commit comments

Comments
 (0)