-
Notifications
You must be signed in to change notification settings - Fork 0
ci: fix release workflow build-id patch and CI hygiene #71
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
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 |
|---|---|---|
|
|
@@ -6,6 +6,10 @@ on: | |
| - main | ||
| workflow_dispatch: | ||
|
|
||
| concurrency: | ||
| group: release-${{ github.ref }} | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| release: | ||
| name: Build and Release APK | ||
|
|
@@ -18,17 +22,20 @@ jobs: | |
|
|
||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| uses: actions/checkout@v7 | ||
| with: | ||
| fetch-depth: 0 # Fetch all history for proper versioning | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Set up Java | ||
| uses: actions/setup-java@v4 | ||
| uses: actions/setup-java@v5 | ||
| with: | ||
| distribution: 'temurin' | ||
| java-version: '17' | ||
|
|
||
| - name: Setup Gradle Build Cache | ||
| uses: gradle/actions/setup-gradle@v6 | ||
|
Comment on lines
+36
to
+37
Contributor
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. 🔒 Security & Privacy | 🔵 Trivial Confirm the cache trust boundary. The new Gradle cache runs in a release job with Verify the triggers and cache scope. Use a trusted or restore-only cache for release runs when required. 🧰 Tools🪛 zizmor (1.29.0)[error] 37-37: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) [error] 37-37: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default (cache-poisoning) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| - name: Set up Flutter | ||
| uses: subosito/flutter-action@v2 | ||
| with: | ||
|
|
@@ -39,7 +46,19 @@ jobs: | |
|
|
||
| - name: Install dependencies | ||
| working-directory: ./workout-logger | ||
| run: flutter pub get | ||
| run: | | ||
| flutter pub get | ||
| : "${PUB_CACHE:?PUB_CACHE is not set}" | ||
| mapfile -t targets < <(find "$PUB_CACHE" -type f -path '*/jni-*/src/CMakeLists.txt') | ||
| if [ "${#targets[@]}" -eq 0 ]; then | ||
| echo "Error: no jni-*/src/CMakeLists.txt files found under \$PUB_CACHE" >&2 | ||
| exit 1 | ||
| fi | ||
| for f in "${targets[@]}"; do | ||
| if ! grep -q -- '-Wl,--build-id=none' "$f"; then | ||
| sed -i -e 's/-Wl,/-Wl,--build-id=none,/' "$f" | ||
| fi | ||
| done | ||
|
Comment on lines
+57
to
+61
Contributor
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. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- workflow context ---'
sed -n '1,130p' .github/workflows/release.yml
printf '%s\n' '--- linker-flag and target references ---'
rg -n -C 4 'targets|build-id|libdartjni|jni|readelf|llvm-readelf|objcopy|strip' .github/workflows/release.yml .github 2>/dev/null || true
printf '%s\n' '--- relevant workflow triggers ---'
sed -n '1,45p' .github/workflows/release.ymlRepository: Devasy/RepForge Length of output: 8793 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- remaining workflow ---'
sed -n '120,280p' .github/workflows/release.yml
printf '%s\n' '--- repository CMake examples ---'
rg -n -C 3 --glob 'CMakeLists.txt' -- '-Wl,|build-id|jni' . || true
printf '%s\n' '--- workflow syntax around the edit and build outputs ---'
rg -n -C 8 'flutter build|gradle|assemble|artifact|release|apk|targets' .github/workflows/release.ymlRepository: Devasy/RepForge Length of output: 10068 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- sed and guard behavior ---'
python3 - <<'PY'
import subprocess
cases = {
"no_linker_anchor": "add_library(foo SHARED foo.c)\n",
"linker_anchor": "target_link_options(foo PRIVATE -Wl,--gc-sections)\n",
"already_disabled": "target_link_options(foo PRIVATE -Wl,--build-id=none,--gc-sections)\n",
}
for name, text in cases.items():
p = subprocess.run(
["sed", "-e", "s/-Wl,/-Wl,--build-id=none,/"],
input=text, text=True, capture_output=True,
)
print(f"{name}: sed_status={p.returncode} changed={p.stdout != text} output={p.stdout.strip()!r}")
print("--- suggested guard outcomes ---")
for name, text in cases.items():
script = r'''
set -e
f=$(mktemp)
trap 'rm -f "$f"' EXIT
printf '%s' "$INPUT" > "$f"
if ! grep -q -- '-Wl,--build-id=none' "$f"; then
if ! grep -qF -- '-Wl,' "$f"; then
exit 1
fi
sed -i -e 's/-Wl,/-Wl,--build-id=none,/' "$f"
fi
grep -qF -- '-Wl,--build-id=none' "$f"
'''
q = subprocess.run(["bash", "-c", script], env={"INPUT": text},
capture_output=True, text=True)
print(f"{name}: guard_status={q.returncode}")
PY
printf '%s\n' '--- Flutter dependency declarations ---'
rg -n -C 3 'jni|native|flutter' workout-logger/pubspec.yaml workout-logger/pubspec.lock 2>/dev/null || trueRepository: Devasy/RepForge Length of output: 13025 🏁 Script executed: #!/bin/bash
set -eu
archive_url='https://pub.dev/api/archives/jni-1.0.0.tar.gz'
printf '%s\n' '--- jni 1.0.0 archive CMake files ---'
curl -fsSL "$archive_url" | tar -tzf - | grep -E '(^|/)CMakeLists\.txt$' || true
printf '%s\n' '--- jni 1.0.0 linker options ---'
curl -fsSL "$archive_url" | tar -xzOf - 2>/dev/null | grep -n -C 3 -- '-Wl,\|build-id' || trueRepository: Devasy/RepForge Length of output: 710 🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
import io, tarfile, urllib.request
for package, version in (("jni", "1.0.0"), ("jni_flutter", "0.ಂತ")):
if package == "jni_flutter":
version = "1.0.0"
url = f"https://pub.dev/api/archives/{package}-{version}.tar.gz"
print(f"--- {package} {version} CMake files and linker options ---")
try:
data = urllib.request.urlopen(url).read()
except Exception as exc:
print(f"download_error={exc}")
continue
with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as tf:
for member in tf.getmembers():
if member.isfile() and member.name.endswith("CMakeLists.txt"):
content = tf.extractfile(member).read().decode("utf-8", "replace")
matches = [
(i + 1, line)
for i, line in enumerate(content.splitlines())
if "-Wl," in line or "build-id" in line
]
print(member.name, matches or "no linker options")
print("--- ELF note assertion syntax ---")
PY
command -v readelf || true
readelf --help 2>/dev/null | grep -E -- '--notes|--section-headers' | head -5 || trueRepository: Devasy/RepForge Length of output: 593 Fail closed when the linker flag is not applied.
🤖 Prompt for AI Agents |
||
|
|
||
| - name: Bump version | ||
| if: github.event_name == 'push' | ||
|
|
@@ -117,6 +136,10 @@ jobs: | |
|
|
||
| - name: Decode release keystore | ||
| run: | | ||
| if [ -z "${{ secrets.KEYSTORE_BASE64 }}" ]; then | ||
| echo "Error: KEYSTORE_BASE64 secret is not configured in repository secrets." | ||
| exit 1 | ||
| fi | ||
|
Comment on lines
+139
to
+142
Contributor
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. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- release workflow lines 1-220 ---'
sed -n '1,220p' .github/workflows/release.yml
printf '%s\n' '--- signing-related references ---'
rg -n -C 3 'KEYSTORE_BASE64|KEYSTORE_PATH|KEY_STORE_PASSWORD|KEY_ALIAS|KEY_PASSWORD|base64|contents:|pull_request|workflow_dispatch|push:' .github/workflows .github 2>/dev/null || trueRepository: Devasy/RepForge Length of output: 14216 🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
import subprocess
unsafe = '''if [ -z "x"; echo INJECTED; #"]; then
echo "missing"
exit 1
fi
echo "decode"
'''
safe = '''if [ -z "$KEYSTORE_BASE64" ]; then
echo "missing"
exit 1
fi
printf '%s' "$KEYSTORE_BASE64" | base64 --decode > /tmp/repforge-release.jks
'''
for name, script in [('direct interpolation', unsafe), ('environment variable', safe)]:
result = subprocess.run(
['bash', '-n'],
input=script,
text=True,
capture_output=True,
)
print(f'{name}: bash -n exit={result.returncode}')
if result.stderr:
print(result.stderr.strip())
PYRepository: Devasy/RepForge Length of output: 300 🌐 Web query:
💡 Result: To prevent shell injection and securely handle secrets in GitHub Actions, you must avoid directly interpolating untrusted inputs or secrets into run commands. The following practices are based on official GitHub security guidance: 1. Use Intermediate Environment Variables Never place expressions like ${{... }} directly into the body of a run command, as they are evaluated and substituted before the shell script executes, which can lead to command injection [1][2]. Instead, map the value (whether it is an untrusted input or a secret) to an intermediate environment variable in the env block [3][4]. Example of the secure pattern: steps: - name: Securely use input or secret env: # Map the value to an environment variable MY_VAR: ${{ github.event.pull_request.title }} MY_SECRET: ${{ secrets.MY_SECRET }} run: | # Use the environment variable inside the script echo "$MY_VAR" echo "$MY_SECRET" 2. Always Quote Variables When referencing environment variables inside your shell scripts, always use double quotes (e.g., "$MY_VAR"). This prevents word splitting and additional shell interpretation issues [3][4][5]. 3. Prefer Arguments over Shell Scripts If possible, use actions (such as JavaScript actions) that accept inputs as arguments rather than constructing shell scripts. This avoids shell interpretation entirely for those values [3][4]. 4. Security Considerations for Secrets - Avoid command-line exposure: Do not pass secrets as command-line arguments to processes, as they may be visible in process listings (e.g., ps command) or security logs [6][7][5]. - Redaction: GitHub automatically redacts many secrets from logs, but this is not guaranteed for all transformations or encodings. Always review logs to ensure secrets are not inadvertently exposed [3][8][9]. - Minimizing scope: Secrets should only be passed to steps or jobs that require them, rather than being accessible to the entire workflow [10]. By treating all inputs and secrets as potential sources of untrusted data and strictly using the environment variable pattern, you significantly harden your workflows against injection attacks [1][2]. Citations:
Pass 🧰 Tools🪛 zizmor (1.29.0)[warning] 139-139: code injection via template expansion (template-injection): may expand into attacker-controllable code (template-injection) 🤖 Prompt for AI AgentsSource: Linters/SAST tools 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- workflow context ---'
sed -n '1,220p' .github/workflows/release.yml
printf '%s\n' '--- release signing configuration ---'
fd -i 'build.gradle.kts' . | while read -r f; do
case "$f" in
*/android/app/build.gradle.kts|*workout-logger/android/app/build.gradle.kts)
printf '\nFILE: %s\n' "$f"
sed -n '1,120p' "$f"
;;
esac
done
printf '%s\n' '--- signing-related references ---'
rg -n -C 3 'KEYSTORE_BASE64|KEYSTORE_PATH|KEY_STORE_PASSWORD|KEY_ALIAS|KEY_PASSWORD|signingConfigs|release' .github workout-logger/android/app 2>/dev/null || trueRepository: Devasy/RepForge Length of output: 20603 🌐 Web query:
💡 Result: In GitHub Actions, when an expression references a secret that is not defined, cannot be accessed, or is otherwise unavailable, it evaluates to an empty string [1][2][3]. This behavior is a common source of silent failures, particularly in conditional logic (e.g., Citations:
🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
workflow = Path(".github/workflows/release.yml").read_text()
gradle = Path("workout-logger/android/app/build.gradle.kts").read_text()
required = ["KEY_STORE_PASSWORD", "KEY_ALIAS", "KEY_PASSWORD"]
workflow_env = {
"KEYSTORE_PATH": "/tmp/repforge-release.jks",
**{name: "" for name in required},
}
print("workflow_build_env:")
for key, value in workflow_env.items():
print(f" {key}={value!r}")
# Model the Kotlin code exactly for the workflow's missing-secret case:
# GitHub Actions supplies the declared env variables as empty strings when
# the corresponding secret expression is empty; System.getenv returns
# strings, not null, for those variables.
all_non_null = all(value is not None for value in workflow_env.values())
store_file = workflow_env["KEYSTORE_PATH"] if all_non_null else None
uses_release = store_file is not None
uses_debug = not uses_release
print(f"all getenv values non-null: {all_non_null}")
print(f"release signing config populated: {all_non_null}")
print(f"release build selects debug fallback: {uses_debug}")
print(f"release build selects release config: {uses_release}")
# Check that the guard covers only the keystore blob and that all three
# credential secrets are passed to the build environment.
guard_lines = [
line.strip() for line in workflow.splitlines()
if "secrets.KEYSTORE_BASE64" in line
]
print("KEYSTORE_BASE64 guard references:")
for line in guard_lines:
print(f" {line}")
for name in required:
print(f"{name} passed to Build APK: {f'{name}: ${{{{ secrets.{name} }}}}' in workflow}")
PYRepository: Devasy/RepForge Length of output: 724 Validate all release-signing secrets. Undefined GitHub secrets become empty strings. The Gradle 🧰 Tools🪛 zizmor (1.29.0)[warning] 139-139: code injection via template expansion (template-injection): may expand into attacker-controllable code (template-injection) 🤖 Prompt for AI Agents |
||
| echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > /tmp/repforge-release.jks | ||
|
|
||
| - name: Build APK | ||
|
|
@@ -126,7 +149,7 @@ jobs: | |
| KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }} | ||
| KEY_ALIAS: ${{ secrets.KEY_ALIAS }} | ||
| KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} | ||
| run: flutter build apk --release --split-per-abi | ||
| run: flutter build apk --release --split-per-abi --obfuscate --split-debug-info=build/app/outputs/symbols | ||
|
Contributor
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. 🗄️ Data Integrity & Integration | 🔵 Trivial Retain the obfuscation symbol maps. The command creates Add private artifact retention or verify that another system stores the symbol maps before shipping obfuscated APKs. 🤖 Prompt for AI Agents |
||
|
|
||
| - name: Rename APKs | ||
| run: | | ||
|
|
@@ -150,7 +173,7 @@ jobs: | |
|
|
||
| - name: Create GitHub Release | ||
| if: github.event_name == 'push' && steps.commit_version.outputs.committed == 'true' | ||
| uses: softprops/action-gh-release@v2 | ||
| uses: softprops/action-gh-release@v3 | ||
| with: | ||
| tag_name: v${{ steps.version.outputs.value }} | ||
| name: RepForge v${{ steps.version.outputs.value }} | ||
|
|
||
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.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Devasy/RepForge
Length of output: 4571
🏁 Script executed:
Repository: Devasy/RepForge
Length of output: 4571
🏁 Script executed:
Repository: Devasy/RepForge
Length of output: 240
🌐 Web query:
GitHub Actions documentation pin actions to a full length commit SHA immutable reference💡 Result:
Pinning GitHub Actions to a full-length commit SHA is a recommended security best practice for ensuring the immutability and integrity of workflow dependencies [1][2]. By referencing a full-length 40-character commit SHA instead of a mutable tag (e.g.,
@v4) or branch name (e.g.,@main), you ensure that your workflow always executes the exact, reviewed version of the code [3][4]. This mitigates the risk of dependency chain abuse, such as a malicious actor compromising an action's repository to update a tag or branch with backdoored code [1][4]. Key points regarding SHA pinning include: Immutability: A full-length commit SHA serves as an immutable reference [1][2]. While tags can be moved or deleted, a specific commit SHA points to a static Git object [1][4]. Security Risk of Short SHAs: You must use the full-length (40-character) SHA [2]. Shortened SHAs are insecure because they are susceptible to collision attacks, where an attacker could force a collision, causing your workflows to fail or potentially execute unauthorized code [2]. Policy Enforcement: GitHub allows administrators to enforce SHA pinning at the repository, organization, and enterprise levels [5][6]. When this policy is enabled, any workflow step using a non-SHA reference (such as a tag or branch) will fail [5][7]. This enforcement covers the entire dependency tree, including internal sub-actions referenced by the primary action [7]. Maintaining Updates: To balance security with maintainability, a common practice is to pin the full SHA while keeping the human-readable version (e.g., the tag) as a trailing comment in the workflow file [4]. This allows automated dependency management tools like Dependabot or Renovate to continue tracking, detecting, and proposing updates for the action [3][4]. For detailed implementation instructions and policy configuration, refer to the official GitHub documentation on security hardening and managing repository/organization actions settings [1][5][6].Citations:
Pin the release workflow actions to full commit SHAs.
The
@v7,@v5,@v6, and@v3references at lines 25, 31, 37, and 176 are mutable tags. Replace each reference with a verified 40-character commit SHA and retain the version tag in a comment. The job hascontents: write, so a moved action tag could execute unreviewed code with release permissions.🧰 Tools
🪛 zizmor (1.29.0)
[error] 25-25: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Source: Linters/SAST tools