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
57 changes: 36 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This GitHub Action enables remote building of iOS applications using [Rock](http
- Build iOS apps for simulator or device
- Automatic artifact caching to speed up builds
- Code signing support for device builds
- Support for additional provisioning profiles (extensions, notifications, etc.)
- Re-signing capability for PR builds
- Native fingerprint-based caching
- Configurable build parameters
Expand Down Expand Up @@ -38,34 +39,48 @@ jobs:
# certificate-file: './certs/distribution.p12'
# certificate-base64: ${{ secrets.CERTIFICATE_BASE64 }}
# certificate-password: ${{ secrets.CERTIFICATE_PASSWORD }}
# provisioning-profile-file: './profiles/app.mobileprovision'
# provisioning-profile-base64: ${{ secrets.PROVISIONING_PROFILE_BASE64 }}
# provisioning-profile-name: 'YourProfileName'
# keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }}
# re-sign: true
# ad-hoc: true
# For apps that require provisioning profiles:
# provisioning-profiles: |
# [
# {
# "name": "NewApp_AdHoc",
# "file": "./profiles/new-app-profile.mobileprovision"
# },
# {
# "name": "ShareExtension",
# "file": "./profiles/share-extension.mobileprovision"
# },
# {
# "name": "NotificationExtension",
# "base64": "${{ secrets.NOTIFICATION_PROFILE_BASE64 }}"
# }
# ]
```

## Inputs

| Input | Description | Required | Default |
| ----------------------------- | ------------------------------------------------------------------------------- | -------- | ----------- |
| `github-token` | GitHub Token | Yes | - |
| `working-directory` | Working directory for the build command | No | `.` |
| `destination` | Build destination: "simulator" or "device" | Yes | `simulator` |
| `scheme` | Xcode scheme | Yes | - |
| `configuration` | Xcode configuration | Yes | - |
| `re-sign` | Re-sign the app bundle with new JS bundle | No | `false` |
| `ad-hoc` | Upload the IPA for ad-hoc distribution to easily install on provisioned devices | No | `false` |
| `certificate-base64` | Base64 encoded P12 file for device builds | No | - |
| `certificate-file` | P12 file for device builds | No | - |
| `certificate-password` | Password for the P12 file | No | - |
| `provisioning-profile-base64` | Base64 encoded provisioning profile | No | - |
| `provisioning-profile-file ` | Provisioning profile file | No | - |
| `provisioning-profile-name` | Name of the provisioning profile | No | - |
| `keychain-password` | Password for temporary keychain | No | - |
| `rock-build-extra-params` | Extra parameters for rock build:ios | No | - |
| `comment-bot` | Whether to comment PR with build link | No | `true` |
| Input | Description | Required | Default |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------- |
| `github-token` | GitHub Token | Yes | - |
| `working-directory` | Working directory for the build command | No | `.` |
| `destination` | Build destination: "simulator" or "device" | Yes | `simulator` |
| `scheme` | Xcode scheme | Yes | - |
| `configuration` | Xcode configuration | Yes | - |
| `re-sign` | Re-sign the app bundle with new JS bundle | No | `false` |
| `ad-hoc` | Upload the IPA for ad-hoc distribution to easily install on provisioned devices | No | `false` |
| `certificate-base64` | Base64 encoded P12 file for device builds | No | - |
| `certificate-file` | P12 file for device builds | No | - |
| `certificate-password` | Password for the P12 file | No | - |
| `provisioning-profile-base64` | Base64 encoded provisioning profile | No | - |
| `provisioning-profile-file` | Provisioning profile file | No | - |
| `provisioning-profile-name` | Name of the provisioning profile | No | - |
| `provisioning-profiles` | JSON array of provisioning profiles. Supports passing PP as both file and base64 string. Supported keys: `name`, `file`, `base64` | No | - |
| `keychain-password` | Password for temporary keychain | No | - |
| `rock-build-extra-params` | Extra parameters for rock build:ios | No | - |
| `comment-bot` | Whether to comment PR with build link | No | `true` |

## Outputs

Expand Down
130 changes: 103 additions & 27 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ inputs:
keychain-password:
description: '[Device Builds] Password that will protect temporary keychain used for signing (can be a random string)'
required: false
provisioning-profiles:
description: '[Device Builds] JSON array of provisioning profiles'
required: false
rock-build-extra-params:
description: 'Extra parameters to pass to "rock build:ios"'
required: false
Expand Down Expand Up @@ -100,32 +103,70 @@ runs:
exit 1
fi

if [ -n "${{ inputs.provisioning-profile-file }}" ] && [ -n "${{ inputs.provisioning-profile-base64 }}" ]; then
echo "Cannot specify both 'provisioning-profile-file' and 'provisioning-profile-base64'. Use one or the other."
exit 1
fi

if [ -z "${{ inputs.provisioning-profile-file }}" ] && [ -z "${{ inputs.provisioning-profile-base64 }}" ]; then
echo "Either 'provisioning-profile-file' or 'provisioning-profile-base64' is required for device builds."
exit 1
fi
# Legacy provisioning profile validation (only when not using provisioning-profiles)
if [ -z "${{ inputs.provisioning-profiles }}" ]; then
if [ -n "${{ inputs.provisioning-profile-file }}" ] && [ -n "${{ inputs.provisioning-profile-base64 }}" ]; then
echo "Cannot specify both 'provisioning-profile-file' and 'provisioning-profile-base64'. Use one or the other."
exit 1
fi

if [ -n "${{ inputs.provisioning-profile-file }}" ]; then
if [ ! -f "${{ inputs.provisioning-profile-file }}" ]; then
echo "Provisioning profile file not found: '${{ inputs.provisioning-profile-file }}'"
if [ -z "${{ inputs.provisioning-profile-file }}" ] && [ -z "${{ inputs.provisioning-profile-base64 }}" ]; then
echo "Either 'provisioning-profile-file' or 'provisioning-profile-base64' is required for device builds when not using 'provisioning-profiles'."
exit 1
fi
fi

if [ -z "${{ inputs.provisioning-profile-name }}" ]; then
echo "Input 'provisioning-profile-name' is required for device builds."
if [ -n "${{ inputs.provisioning-profile-file }}" ]; then
if [ ! -f "${{ inputs.provisioning-profile-file }}" ]; then
echo "Provisioning profile file not found: '${{ inputs.provisioning-profile-file }}'"
exit 1
fi
fi
fi

# Check if either provisioning-profile-name or provisioning-profiles is provided
if [ -z "${{ inputs.provisioning-profile-name }}" ] && [ -z "${{ inputs.provisioning-profiles }}" ]; then
echo "Either 'provisioning-profile-name' or 'provisioning-profiles' is required for device builds."
exit 1
fi

if [ -n "${{ inputs.provisioning-profile-name }}" ] && [ -n "${{ inputs.provisioning-profiles }}" ]; then
echo "Cannot specify both 'provisioning-profile-name' and 'provisioning-profiles'. Use one or the other."
exit 1
fi

if [ -z "${{ inputs.keychain-password }}" ]; then
echo "Input 'keychain-password' is required for device builds."
exit 1
fi

# Validate provisioning profiles if provided
if [ -n "${{ inputs.provisioning-profiles }}" ]; then
while read -r profile; do
name=$(echo "$profile" | jq -r '.name')
file_path=$(echo "$profile" | jq -r '.file // empty')
base64_content=$(echo "$profile" | jq -r '.base64 // empty')

if [ -z "$name" ]; then
echo "Provisioning profile missing 'name' field"
exit 1
fi

if [ -n "$file_path" ] && [ -n "$base64_content" ]; then
echo "Cannot specify both 'file' and 'base64' for profile '$name'"
exit 1
fi

if [ -z "$file_path" ] && [ -z "$base64_content" ]; then
echo "Either 'file' or 'base64' is required for profile '$name'"
exit 1
fi

if [ -n "$file_path" ] && [ ! -f "$file_path" ]; then
echo "Provisioning profile file not found: '$file_path'"
exit 1
fi
done < <(echo "${{ inputs.provisioning-profiles }}" | jq -c '.[]')
fi
fi
shell: bash

Expand Down Expand Up @@ -223,18 +264,39 @@ runs:
echo "Certificate identity: $IDENTITY"
echo "IDENTITY=$IDENTITY" >> $GITHUB_ENV

# Unpack provisioning profile
# Unpack provisioning profile (legacy single profile support)
PROFILE_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
PROFILE_PATH="$PROFILE_DIR/${{ inputs.provisioning-profile-name }}.mobileprovision"

mkdir -p "$PROFILE_DIR"

if [ -n "${{ inputs.provisioning-profile-file }}" ]; then
# Use provisioning profile file directly
cp "${{ inputs.provisioning-profile-file }}" "$PROFILE_PATH"
else
# Decode base64 provisioning profile
echo -n "${{ inputs.provisioning-profile-base64 }}" | base64 --decode -o "$PROFILE_PATH"
if [ -n "${{ inputs.provisioning-profile-name }}" ]; then
PROFILE_PATH="$PROFILE_DIR/${{ inputs.provisioning-profile-name }}.mobileprovision"

if [ -n "${{ inputs.provisioning-profile-file }}" ]; then
# Use provisioning profile file directly
cp "${{ inputs.provisioning-profile-file }}" "$PROFILE_PATH"
else
# Decode base64 provisioning profile
echo -n "${{ inputs.provisioning-profile-base64 }}" | base64 --decode -o "$PROFILE_PATH"
fi
fi

# Setup provisioning profiles
if [ -n "${{ inputs.provisioning-profiles }}" ]; then
while read -r profile; do
name=$(echo "$profile" | jq -r '.name')
file_path=$(echo "$profile" | jq -r '.file // empty')
base64_content=$(echo "$profile" | jq -r '.base64 // empty')

ADDITIONAL_PROFILE_PATH="$PROFILE_DIR/${name}.mobileprovision"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Path Traversal Vulnerability in Profile Naming

The name field in the additional-provisioning-profiles input is used directly when constructing file paths for provisioning profiles. This creates a path traversal vulnerability, potentially allowing an attacker to write files to arbitrary locations outside the intended directory.

Fix in Cursor Fix in Web

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thymikee Cursor suggest to add sanitize function for name - which removes all characters except alphanumeric, dots, underscores, and hyphens, especially removes all leading dots and
trailing dots. So we can prevent potential attack. Do you think we should go with it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really a vulnerability?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory someone could pass the name like ../../../etc/passwd so the certificate would be saved somewhere we don't have control, but as it is on CI I don't think it would be an issue


if [ -n "$file_path" ]; then
cp "$file_path" "$ADDITIONAL_PROFILE_PATH"
else
echo -n "$base64_content" | base64 --decode -o "$ADDITIONAL_PROFILE_PATH"
fi

echo "Installed provisioning profile: $name"
done < <(echo "${{ inputs.provisioning-profiles }}" | jq -c '.[]')
fi
shell: bash

Expand Down Expand Up @@ -395,9 +457,23 @@ runs:
CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12
rm "$CERTIFICATE_PATH"

PROFILE_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
PROFILE_PATH="$PROFILE_DIR/${{ inputs.provisioning-profile-name }}.mobileprovision"
rm "$PROFILE_PATH"
# Clean up legacy single provisioning profile
if [ -n "${{ inputs.provisioning-profile-name }}" ]; then
PROFILE_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
PROFILE_PATH="$PROFILE_DIR/${{ inputs.provisioning-profile-name }}.mobileprovision"
rm "$PROFILE_PATH"
fi

# Clean up provisioning profiles
if [ -n "${{ inputs.provisioning-profiles }}" ]; then
PROFILE_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
while read -r profile; do
name=$(echo "$profile" | jq -r '.name')
PROFILE_PATH="$PROFILE_DIR/${name}.mobileprovision"
rm "$PROFILE_PATH"
echo "Cleaned up additional provisioning profile: $name"
done < <(echo "${{ inputs.provisioning-profiles }}" | jq -c '.[]')
fi
shell: bash

- name: Cleanup Cache
Expand Down