Skip to content
Closed
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
46 changes: 46 additions & 0 deletions .github/workflows/create-release-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Create Release PR

on:
workflow_dispatch:

permissions:
contents: read
pull-requests: write

jobs:
create-release-pr:
name: Create Release PR
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ secrets.DEVSY_GITHUB_APP_ID }}
private-key: ${{ secrets.DEVSY_GITHUB_APP_PRIVATE_KEY }}

- uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
Comment on lines +15 to +24

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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

find . -name "create-release-pr.yml" -type f

Repository: devsy-org/devsy

Length of output: 101


🏁 Script executed:

cat -n .github/workflows/create-release-pr.yml

Repository: devsy-org/devsy

Length of output: 1490


🌐 Web query:

GitHub Actions security best practices pinning commits SHAs vs version tags

💡 Result:

Best practice (for security): pin GitHub Actions to the full-length commit SHA of the action (not a version tag) when you want an immutable, deterministic dependency. Why SHAs are preferred over version tags 1) Tags are mutable pointers, while a full commit SHA is immutable. GitHub explicitly states that pinning an action to a full-length commit SHA is the only way to use an action as an immutable release, and it helps mitigate risk from a bad actor modifying the action’s repository. [1] 2) Even if you trust the action author, GitHub notes there is still risk with tag-based references because a tag can be moved or deleted if an attacker gains access. [1] 3) GitHub’s 2025 policy/enforcement feature reflects this security model: GitHub recommends pinning dependency versions to a specific commit SHA to prevent malicious code added to a new or updated branch/tag from being automatically used, and it can enforce workflows to fail unless actions are pinned to a full commit SHA. [2] When version tags are acceptable - GitHub’s documentation allows using tags for convenience, but explicitly frames it as less secure: “Pin actions to a tag only if you trust the creator.” [1] Operational guidance you should pair with SHA pinning - Verify the SHA belongs to the action’s repository (not a fork). GitHub recommends confirming that the selected SHA is from the action repository and not a repository fork. [1] - Keep pinned SHAs up to date using tooling such as Dependabot/Renovate (GitHub notes Dependabot creates alerts for actions pinned to SHAs). [1] - Consider organizational controls: GitHub supports configuring policies to require SHA pinning. [1][2] Practical takeaway for your workflows - Use: owner/repo@ for third-party actions you do not fully control. - Prefer version tags only when you explicitly accept the tradeoff (e.g., you trust the creator and understand that the tag can change).

Citations:


🌐 Web query:

GitHub Actions persist-credentials false checkout security

💡 Result:

In GitHub Actions, the persist-credentials input for the actions/checkout action controls whether the repository's authentication token (typically GITHUB_TOKEN) is stored in the local Git configuration [1]. By default, this is set to true, which enables subsequent steps in the same job to execute authenticated Git commands like git push or git fetch automatically [1]. From a security perspective, setting persist-credentials to false is a recommended best practice to minimize the risk of credential exposure [2][3][4]. When set to true, any compromised action or malicious script running within the same job can read the stored token from the disk, potentially allowing an attacker to access or modify the repository [2][3]. Recent updates have improved how these credentials are handled: 1. Improved Security Mechanism: Instead of storing the token directly in the repository's.git/config file, newer versions of actions/checkout store credentials in a separate file within the $RUNNER_TEMP directory [5][1]. This change uses Git's includeIf mechanism to isolate the credentials, reducing the risk of accidental exposure [5]. 2. Hardening Workflows: Because the default behavior can still present a security risk if an action in the pipeline is compromised, many organizations now enforce persist-credentials: false across all their workflows [2][6]. If you disable persist-credentials (set it to false), you must manually configure credentials if your workflow requires authenticated Git operations [4]. For example, if you use an action that automatically commits changes (like a git-auto-commit action), you will need to re-authenticate or provide a token explicitly before that step runs [4]. To further secure your workflows, always consider the principle of least privilege, such as setting default GITHUB_TOKEN permissions to read-only at the repository or organization level [7]. Additionally, pin your actions to immutable commit SHAs rather than version tags to protect against supply chain attacks involving tag hijacking [8][9].

Citations:


Pin workflow actions to commit SHAs and disable credential persistence.

Lines 15 and 21 use mutable version tags (@v3, @v6). Per GitHub security guidance, pin actions to full-length commit SHAs as the only way to ensure immutable, deterministic action versions and mitigate supply chain risks. Additionally, add persist-credentials: false to the checkout step to prevent the app token from being stored in the git config, reducing credential exposure risk if a subsequent action is compromised.

Suggested patch
-      - uses: actions/create-github-app-token@v3
+      - uses: actions/create-github-app-token@<pinned-sha>
         id: app-token
         with:
           app-id: ${{ secrets.DEVSY_GITHUB_APP_ID }}
           private-key: ${{ secrets.DEVSY_GITHUB_APP_PRIVATE_KEY }}

-      - uses: actions/checkout@v6
+      - uses: actions/checkout@<pinned-sha>
         with:
           fetch-depth: 0
           token: ${{ steps.app-token.outputs.token }}
+          persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 21-24: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


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

(unpinned-uses)


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

(unpinned-uses)


[error] 15-15: 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/create-release-pr.yml around lines 15 - 24, Replace the
mutable action tags with pinned full-length commit SHAs and disable credential
persistence: update the uses for actions/create-github-app-token (id: app-token)
and actions/checkout to reference their specific commit SHAs instead of `@v3/`@v6,
and add persist-credentials: false to the actions/checkout step that currently
uses token: ${{ steps.app-token.outputs.token }} so the app token is not stored
in git config.


- name: Get commit log
id: commits
run: |
COMMITS=$(git log release..main --oneline)
{
echo "log<<EOF"
echo "$COMMITS"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Create PR
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
gh pr create \
--base release \
--head main \
--title "release: promote main to stable" \
--body "## Commits being promoted

${{ steps.commits.outputs.log }}"
Comment on lines +26 to +46

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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/create-release-pr.yml | head -60

Repository: devsy-org/devsy

Length of output: 1490


🌐 Web query:

gh pr create --body-file option documentation

💡 Result:

The --body-file flag for the GitHub CLI command gh pr create allows you to read the body text of a pull request from a specified file [1][2]. Usage: gh pr create --body-file Key details: - You can use "-" as the filename to read the body text from standard input (stdin) [1][2]. - This flag is particularly useful for creating pull requests with long or multi-line bodies, as it helps avoid issues with command-line length limits in certain environments (such as Windows) [3]. - It was introduced to facilitate the automation of pull request creation using shell scripts or other non-interactive contexts [3][4]. - If you need to use a pull request template as a starting point, you can use the --template flag instead [1][2], though the --body-file flag can be used to provide the full content directly [5]. This option is part of the standard GitHub CLI (gh) documentation [1][6].

Citations:


🏁 Script executed:

cd /tmp && git init test-repo && cd test-repo && git config user.email "test@test.com" && git config user.name "Test" && \
git checkout -b release && echo "file" > file.txt && git add . && git commit -m "initial" && \
git checkout -b main && echo "change" >> file.txt && git commit -am "change" && \
git log release..main 2>&1 | head -5 && echo "---" && \
git log origin/release..origin/main 2>&1 | head -5

Repository: devsy-org/devsy

Length of output: 1243


🏁 Script executed:

# Check if typical commit messages could be problematic
cat <<'EOF'
Examples of problematic commit messages that could cause shell injection:
1. "fix: $(rm -rf /)" 
2. "refactor: `dangerous command`"
3. "docs: $(whoami)"
These would be evaluated if passed through bash string interpolation
EOF

Repository: devsy-org/devsy

Length of output: 281


Fix shell injection vulnerability in PR body construction.

The --body parameter interpolates commit messages into a bash command string. Commit subjects containing shell metacharacters like $(...) or backticks would be evaluated by the shell before passing to gh pr create, creating a shell injection vector. Use --body-file to bypass shell evaluation:

Suggested patch
-      - name: Get commit log
-        id: commits
-        run: |
-          COMMITS=$(git log release..main --oneline)
-          {
-            echo "log<<EOF"
-            echo "$COMMITS"
-            echo "EOF"
-          } >> "$GITHUB_OUTPUT"
-
       - name: Create PR
         env:
           GH_TOKEN: ${{ steps.app-token.outputs.token }}
         run: |
+          {
+            echo "## Commits being promoted"
+            echo
+            git log release..main --oneline
+          } > pr_body.md
+
           gh pr create \
             --base release \
             --head main \
             --title "release: promote main to stable" \
-            --body "## Commits being promoted
-
-          ${{ steps.commits.outputs.log }}"
+            --body-file pr_body.md

Note: The origin/release..origin/main suggestion in the original comment may fail in this workflow. With actions/checkout@v6, branches are local refs. Keep release..main as shown above, or explicitly fetch remote-tracking branches if you want to reference origin/ prefixed refs.

🧰 Tools
🪛 zizmor (1.25.2)

[info] 46-46: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 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/create-release-pr.yml around lines 26 - 46, Replace the
unsafe inline interpolation of commit text into the gh pr create --body flag
with gh pr create --body-file to avoid shell evaluation; in the "Create PR" step
(env GH_TOKEN, using gh pr create) write the contents of
steps.commits.outputs.log to a securely created temporary file (using a safe
write like printf '%s' or redirect from the steps output) and pass that filename
to --body-file, ensure proper quoting when writing, and remove the temp file
after the command; keep the "Get commit log" step (id: commits) as-is so you can
reference steps.commits.outputs.log for the file contents.

3 changes: 2 additions & 1 deletion .github/workflows/semantic-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ jobs:
node-version: 22
cache: npm

- run: npm install
- run: npm ci

- name: Run semantic-release
run: npx semantic-release ${{ inputs.dry-run && '--dry-run' || '' }}
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
# Unit test targets
/main
/profile.out
/package-lock.json
/tagsvendor/
node_modules/
desktop/dist/
Expand Down
9 changes: 1 addition & 8 deletions .releaserc.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
{
"tagFormat": "v${version}",
"branches": [
{ "name": "main", "prerelease": "rc" },
{ "name": "release", "prerelease": false }
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
["@semantic-release/changelog", { "changelogFile": "CHANGELOG.md" }],
[
"@semantic-release/github",
{ "successComment": false, "failTitle": false }
],
[
"@semantic-release/git",
{
"assets": ["CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
}
791 changes: 0 additions & 791 deletions CHANGELOG.md

This file was deleted.

Loading
Loading