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
26 changes: 24 additions & 2 deletions .github/blocks/swift-build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ inputs:
description: "Space-separated list of SPM resource bundle names to include (e.g. 'Foo_Foo.bundle')"
required: false
default: ""
entitlements:
description: "Path to an entitlements plist (relative to repo root). When set, the built binary is ad-hoc codesigned with these entitlements. Needed for daemons that require private entitlements (e.g. to connect to imagent)."
required: false
default: ""
github-token:
description: "Token with read access to private/internal SPM dependencies. When set, git is configured to use it for github.com clones (so SwiftPM can fetch private deps)."
required: false
default: ""

outputs:
binary-path:
Expand Down Expand Up @@ -49,13 +57,27 @@ runs:
- name: Create artifacts directory
run: mkdir -p artifacts
shell: bash
- name: Configure git auth for private/internal SPM dependencies
if: inputs.github-token != ''
shell: bash
env:
GH_TOKEN: ${{ inputs.github-token }}
run: |
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/"
Comment on lines +60 to +66
- name: Build Swift Binary
shell: bash
id: build
run: |
swift build -c release
cp ".build/release/${{ inputs.binary-name }}" artifacts
echo "binary-path=.build/release/${{ inputs.binary-name }}" >> $GITHUB_OUTPUT
BIN=".build/release/${{ inputs.binary-name }}"
if [ -n "${{ inputs.entitlements }}" ]; then
echo "::group::Codesign $BIN (entitlements: ${{ inputs.entitlements }})"
codesign -s - --entitlements "${{ inputs.entitlements }}" -f "$BIN"
codesign -dvvv "$BIN" || true
echo "::endgroup::"
fi
cp "$BIN" artifacts
echo "binary-path=$BIN" >> $GITHUB_OUTPUT

# ── Collect resource bundles ──
BUNDLES_PATH=""
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/swift-pkg-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ on:
required: false
default: ""
description: "Space-separated list of SPM resource bundle names to include in the .pkg"
entitlements:
type: string
required: false
default: ""
description: "Path to an entitlements plist for ad-hoc codesigning the built binary."
private-deps:
type: boolean
required: false
default: false
description: "Mint an app token (APP_ID/APP_PRIVATE_KEY) so SwiftPM can clone private/internal org dependencies."
secrets:
SECRET_ENV_VARS:
required: false
Expand Down Expand Up @@ -54,12 +64,24 @@ jobs:
| **Commit** | ${{ github.sha }} |
| **Run** | [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) |

# ── Mint app token for private SPM dependencies (optional) ───────────
- name: Mint app token for private SPM deps
id: app-token
if: inputs.private-deps
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
Comment on lines +68 to +75

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# List relevant workflow files
ls -la .github/workflows || true

# Show the relevant sections of the PR workflow
echo "---- swift-pkg-pr.yml: lines 1-140 ----"
nl -ba .github/workflows/swift-pkg-pr.yml | sed -n '1,140p'

# If present, show release workflow too
if [ -f .github/workflows/swift-release.yml ]; then
  echo "---- swift-release.yml: lines 1-200 ----"
  nl -ba .github/workflows/swift-release.yml | sed -n '1,200p'
else
  echo "---- swift-release.yml not found ----"
fi

# Grep for the app token output usage and github-token passing
echo "---- usages of app-token and create-github-app-token ----"
rg -n "create-github-app-token|id:\s*app-token|steps\.app-token\.outputs\.token|private-deps" .github/workflows/swift-pkg-pr.yml .github/workflows/swift-release.yml || true

Repository: photon-hq/buildspace

Length of output: 1531


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE_PR=".github/workflows/swift-pkg-pr.yml"
FILE_REL=".github/workflows/swift-release.yml"

echo "---- Token step vicinity (swift-pkg-pr.yml) ----"
awk 'NR>=45 && NR<=110 {printf "%d\t%s\n", NR, $0}' "$FILE_PR"

echo "---- Inputs/secrets contract (swift-pkg-pr.yml) ----"
awk 'NR>=1 && NR<=120 {printf "%d\t%s\n", NR, $0}' "$FILE_PR" | sed -n '1,120p'

echo "---- Find create-github-app-token usage (PR) ----"
rg -n "create-github-app-token|app-id:|private-key:|permission-|repositories:|steps\.app-token\.outputs\.token|github-token" "$FILE_PR" || true

if [ -f "$FILE_REL" ]; then
  echo "---- Token step vicinity (swift-release.yml) ----"
  awk 'NR>=1 && NR<=200 {printf "%d\t%s\n", NR, $0}' "$FILE_REL" | sed -n '1,200p'
  echo "---- Find create-github-app-token usage (Release) ----"
  rg -n "create-github-app-token|app-id:|private-key:|permission-|repositories:|steps\.app-token\.outputs\.token|github-token" "$FILE_REL" || true
else
  echo "swift-release.yml not found"
fi

Repository: photon-hq/buildspace

Length of output: 16600


🌐 Web query:

actions/create-github-app-token owner repositories permission-contents behavior token scope all repositories in installation

💡 Result:

The actions/create-github-app-token action allows for flexible scoping of GitHub App installation tokens within workflows [1][2]. The behavior of the token depends on the combination of the owner and repositories inputs [1][3]: - All repositories in installation: If the owner input is provided and the repositories input is empty, the generated token is scoped to all repositories available in that GitHub App installation [1][2]. - Specific repositories: If the repositories input is provided (as a comma or newline-separated list), the token is restricted to those specific repositories [2][4]. - Default (single repository): If both owner and repositories inputs are omitted, the token is scoped only to the current repository where the workflow is running [1][3]. Regarding permissions, the token inherits all permissions defined in the GitHub App's installation [1][3]. However, the action provides explicit input parameters (e.g., permission-contents, permission-issues, permission-pull-requests) that allow you to limit the token's permissions further [5][6]. It is a recommended security practice to explicitly list only the permissions required for your specific use case to follow the principle of least privilege [3][7]. Note that installation permissions must be approved by an account administrator if the app adds new permissions after the initial installation [1]. By default, the action automatically revokes the generated token after the job completes unless the skip-token-revoke input is set to true [1][4].

Citations:


🌐 Web query:

GitHub reusable workflows workflow_call secrets: inherit secrets not declared in on.workflow_call.secrets still accessible

💡 Result:

Yes, when you use secrets: inherit in a calling workflow, all secrets available to the calling workflow are passed to the reusable workflow, making them accessible even if they are not explicitly declared in the on.workflow_call.secrets section of the reusable workflow [1][2]. When secrets: inherit is used, the reusable workflow gains access to the caller's secrets at runtime [3]. This means that within your reusable workflow steps, you can directly reference secrets using the ${{ secrets.SECRET_NAME }} syntax, regardless of whether that specific secret was explicitly defined in the reusable workflow's on.workflow_call.secrets block [1][2]. Key considerations regarding this behavior include: 1. Runtime Availability: While the secrets are available at runtime for use in steps, they are not automatically available in other contexts where static validation occurs, such as in the with: block of a job call [3]. 2. Scope: Secrets are only passed to directly called workflows; they do not automatically propagate further down a chain of nested reusable workflows unless they are explicitly passed at each stage [1][2]. 3. Security/Visibility: Because secrets are passed implicitly, you do not need to update the reusable workflow's on.workflow_call configuration whenever a new secret is added to the calling repository [4]. However, this also means the reusable workflow can theoretically access any secret the caller possesses, which may be a consideration depending on your security model [4]. For these reasons, inherit is often used to simplify maintenance for reusable workflows that need to access dynamic or growing sets of secrets without requiring frequent updates to the workflow definition [4].

Citations:


Restrict the GitHub App token scope before passing it to the Swift build (in both PR + release workflows)

In both .github/workflows/swift-pkg-pr.yml and .github/workflows/swift-release.yml, actions/create-github-app-token@v1 is called with owner but no repositories allowlist, so the token is scoped to all repositories in the GitHub App installation and inherits the installation’s permissions—then that broad token is passed as github-token into the Swift build. Tighten this with an allowlist and least-privilege repo/content permissions (e.g., permission-contents: read). (github.com/actions/create-github-app-token)

  • Also add APP_ID / APP_PRIVATE_KEY to each workflow’s workflow_call.secrets (or explicitly require callers to use secrets: inherit), since the workflows reference those secrets even though they’re not declared in on.workflow_call.secrets.
🔐 One way to scope the token down
       private-deps:
         type: boolean
         required: false
         default: false
         description: "Mint an app token (APP_ID/APP_PRIVATE_KEY) so SwiftPM can clone private/internal org dependencies."
+      private-deps-repositories:
+        type: string
+        required: false
+        default: ""
+        description: "Comma/newline-separated repository allowlist for the GitHub App token."

       - name: Mint app token for private SPM deps
         id: app-token
         if: inputs.private-deps
         uses: actions/create-github-app-token@v1
         with:
           app-id: ${{ secrets.APP_ID }}
           private-key: ${{ secrets.APP_PRIVATE_KEY }}
           owner: ${{ github.repository_owner }}
+          repositories: ${{ inputs.private-deps-repositories }}
+          permission-contents: read
🧰 Tools
🪛 actionlint (1.7.12)

[error] 73-73: property "app_id" is not defined in object type {actions_runner_debug: string; actions_step_debug: string; github_token: string; secret_env_vars: string}

(expression)


[error] 74-74: property "app_private_key" is not defined in object type {actions_runner_debug: string; actions_step_debug: string; github_token: string; secret_env_vars: string}

(expression)

🪛 zizmor (1.25.2)

[error] 71-71: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 75-75: dangerous use of GitHub App tokens (github-app): token granted access to all repositories for this owner's app installation

(github-app)


[error] 71-71: dangerous use of GitHub App tokens (github-app): app token inherits blanket installation permissions

(github-app)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/swift-pkg-pr.yml around lines 68 - 75, The GitHub App
token created by the step using actions/create-github-app-token@v1 (step id
"app-token") is currently scoped to the whole installation; update that step in
both swift-pkg-pr.yml and swift-release.yml to include a repositories allowlist
(the repositories input) and restrict permissions (e.g., permission-contents:
read) so the token has least privilege before it is passed into the Swift build
(github-token). Also declare APP_ID and APP_PRIVATE_KEY in each workflow's
workflow_call.secrets (or require callers to use secrets: inherit) so those
secrets are explicitly accepted by the reusable workflow.


# ── Build ────────────────────────────────────────────────────────────
- name: Swift Build
id: build
uses: photon-hq/buildspace/.github/blocks/swift-build@main
with:
binary-name: ${{ inputs.package-name }}
entitlements: ${{ inputs.entitlements }}
github-token: ${{ steps.app-token.outputs.token }}
compile-env: |-
VERSION=PR-${{ github.event.pull_request.number }}
DEPLOYMENT_ENVIRONMENT=development
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/swift-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ on:
required: false
default: ""
description: "Space-separated list of SPM resource bundle names to include in the .pkg"
entitlements:
type: string
required: false
default: ""
description: "Path to an entitlements plist for ad-hoc codesigning the built binary."
private-deps:
type: boolean
required: false
default: false
description: "Mint an app token (APP_ID/APP_PRIVATE_KEY) so SwiftPM can clone private/internal org dependencies."
labels-to-check:
type: string
required: false
Expand Down Expand Up @@ -115,12 +125,22 @@ jobs:
needs: prepare-release
runs-on: macos-26
steps:
- name: Mint app token for private SPM deps
id: app-token
if: inputs.private-deps
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
Comment on lines +131 to +135
- name: Swift Build
id: build
uses: photon-hq/buildspace/.github/blocks/swift-build@main
with:
binary-name: ${{ inputs.package-name }}
use-cache: "false"
entitlements: ${{ inputs.entitlements }}
github-token: ${{ steps.app-token.outputs.token }}
compile-env: |-
VERSION=${{ needs.prepare-release.outputs.version }}
DEPLOYMENT_ENVIRONMENT=production
Expand Down
Loading