Skip to content

Update repository workflow with hardening and security improvements#130

Merged
igorpecovnik merged 1 commit intomainfrom
repohandling
Jan 2, 2026
Merged

Update repository workflow with hardening and security improvements#130
igorpecovnik merged 1 commit intomainfrom
repohandling

Conversation

@igorpecovnik
Copy link
Member

@igorpecovnik igorpecovnik commented Jan 2, 2026

Summary

  • Add comprehensive input validation for all paths and parameters
  • Implement SSRF prevention for API calls
  • Add format validation for hostnames, usernames, and ports
  • Enhance NetBox API error handling and validation
  • Improve permission management with safety checks
  • Add dry-run support for sync operations
  • Strengthen repository path validation throughout
  • Enhance security for beta repository operations

Changes

This PR consolidates security improvements to the repository update workflow, including:

  • Hardened input validation across all workflow steps
  • SSRF prevention for external API calls
  • Enhanced error handling with proper validation
  • Improved security for file system operations

Test plan

  • Review all security validations
  • Test workflow with various inputs
  • Verify dry-run mode works correctly

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 2, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

The workflow .github/workflows/infrastructure-repository-update.yml was renamed and refactored into a multi-stage pipeline. Environment variables (STORAGE_PATH, PUBLISHING_PATH, CLEANUP_INPUT, DRY_RUN_SYNC) replaced workflow_dispatch inputs. A new fix-permissions job validates paths and sets ACLs; prepare-beta recreates the beta repo. The main flow runs a repository matrix (debs, debs-beta) with GPG key imports, repo updates, and conditional .deb counts. Preclean produces a release/package JSON matrix; postclean runs per pair. Significant additions query NetBox for host/target data, add validation guards (SSRF/path traversal prevention), and implement per-host, per-target rsync/indexing with detailed step summaries and dry-run support.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • armbianworker

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary focus of the changes: adding hardening and security improvements to a repository workflow, which aligns with the extensive security validations, SSRF prevention, and permission checks introduced throughout the PR.
Description check ✅ Passed The description is directly related to the changeset, covering the main security improvements: input validation, SSRF prevention, NetBox API hardening, permission management, and dry-run support—all reflected in the workflow modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2f06441 and b865c96.

📒 Files selected for processing (1)
  • .github/workflows/infrastructure-repository-update.yml

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (7)
.github/workflows/infrastructure-repository-update.yml (7)

163-174: Redundant directory check.

Line 163 already confirms $BETA_REPO_PATH is a directory with -d, so the symlink/directory check on line 167 is partially redundant. However, the symlink check adds value since -d returns true for symlinks to directories.

The comment about the trailing slash "preventing accidental matches" is slightly misleading—the slash ensures the path is treated as a directory but doesn't prevent pattern matches. Consider updating the comment for accuracy.


207-215: Duplicate step names reduce log clarity.

Both steps are named "Import GPG key" but import different keys. Consider distinct names for easier debugging.

🔎 Proposed fix
-      - name: Import GPG key
+      - name: Import GPG key (primary)
         uses: crazy-max/ghaction-import-gpg@v6
         with:
           gpg_private_key: ${{ secrets.GPG_KEY3 }}

-      - name: Import GPG key
+      - name: Import GPG key (secondary)
         uses: crazy-max/ghaction-import-gpg@v6
         with:
           gpg_private_key: ${{ secrets.GPG_KEY4 }}

599-608: Redundant empty string checks.

The -z test already checks for empty strings, making the || "..." == "" condition redundant.

🔎 Simplified validation
-          if [[ -z "${{ secrets.NETBOX_API }}" || "${{ secrets.NETBOX_API }}" == "" ]]; then
+          if [[ -z "${{ secrets.NETBOX_API }}" ]]; then
             echo "::error::NETBOX_API secret is not set or is empty"
             exit 1
           fi

-          if [[ -z "${{ secrets.NETBOX_TOKEN }}" || "${{ secrets.NETBOX_TOKEN }}" == "" ]]; then
+          if [[ -z "${{ secrets.NETBOX_TOKEN }}" ]]; then
             echo "::error::NETBOX_TOKEN secret is not set or is empty"
             exit 1
           fi

808-834: Word splitting in array assignment is fragile.

The pattern TARGETS=($(echo ... | jq ...)) relies on word splitting, which breaks if tag names contain spaces or special characters. While the current tags (debs, debs-beta) are safe, this pattern is brittle.

🔎 Proposed fix using mapfile
-          # Extract targets
-          TARGETS=($(echo "$response" | jq -r '.results[] | .tags[] | .name' 2>/dev/null | grep -v "Push" || echo ""))
+          # Extract targets safely using mapfile
+          mapfile -t TARGETS < <(echo "$response" | jq -r '.results[] | .tags[] | .name' 2>/dev/null | grep -v "Push" || true)

866-898: Rsync command duplication risks log/execution mismatch.

The RSYNC_CMD string logged doesn't execute; the actual rsync runs separately with duplicated arguments. If one is updated without the other, logs become misleading.

Consider constructing arguments in an array to avoid duplication:

🔎 Proposed approach using array
RSYNC_ARGS=(
  $RSYNC_OPTIONS
  -e "ssh -p ${SERVER_PORT} -o StrictHostKeyChecking=accept-new -o ConnectTimeout=30"
  --exclude "dists" --exclude "control"
  "$REPO_PATH/public/"
  "${SERVER_USERNAME}@${HOSTNAME}:${SERVER_PATH}/apt"
)
echo "Command: rsync ${RSYNC_ARGS[*]}" | tee -a "$GITHUB_STEP_SUMMARY"
rsync "${RSYNC_ARGS[@]}"

1073-1103: Index job silently skips debs target with a warning.

The loop iterates over all TARGETS (including debs), but the case statement only handles debs-beta, causing debs to fall through to the "Unknown target" warning. If this is intentional (only beta needs finalization), consider filtering before the loop or adding a no-op case for debs:

🔎 Proposed fix
           for target in "${TARGETS[@]}"; do
             echo "→ Finalizing $target" | tee -a "$GITHUB_STEP_SUMMARY"

             case "$target" in
+              debs)
+                # Main repository doesn't need finalization step
+                echo "Skipping $target (no finalization needed)" | tee -a "$GITHUB_STEP_SUMMARY"
+                ;;
               debs-beta)
                 REPO_PATH="${PUBLISHING_PATH}-debs-beta"
                 # ... existing logic ...
                 ;;
               *)
                 echo "::warning::Unknown target: $target"
                 ;;
             esac
           done

860-864: Host key removal runs inside target loop.

ssh-keygen -R is executed for each target, but only needs to run once per hostname. Consider moving it before the loop:

🔎 Proposed fix
+          # Remove old host key once before syncing all targets
+          ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "$HOSTNAME" 2>/dev/null || true
+
           # Sync to each target
           for target in "${TARGETS[@]}"; do
             echo "→ Syncing $target" | tee -a "$GITHUB_STEP_SUMMARY"
-
-            # Remove old host key
-            ssh-keygen -f "${HOME}/.ssh/known_hosts" -R "$HOSTNAME" 2>/dev/null || true
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5c88453 and 2f06441.

📒 Files selected for processing (1)
  • .github/workflows/infrastructure-repository-update.yml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-04T22:48:41.167Z
Learnt from: igorpecovnik
Repo: armbian/armbian.github.io PR: 85
File: .github/workflows/generate-keyring-data.yaml:75-85
Timestamp: 2025-11-04T22:48:41.167Z
Learning: In `.github/workflows/generate-keyring-data.yaml` for the Armbian project: `ubuntu-keyring-ports` is intentionally optional because they're not certain it's needed, while `debian-ports-archive-keyring` must be mandatory because it's always available in Debian sid and is required for the project.

Applied to files:

  • .github/workflows/infrastructure-repository-update.yml
🪛 actionlint (1.7.9)
.github/workflows/infrastructure-repository-update.yml

56-56: label "repository" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file

(runner-label)


134-134: label "repository" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file

(runner-label)


195-195: label "repository" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file

(runner-label)


292-292: label "repository" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file

(runner-label)


356-356: label "repository" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file

(runner-label)


502-502: label "repository" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file

(runner-label)

🔇 Additional comments (5)
.github/workflows/infrastructure-repository-update.yml (5)

62-127: Solid permission handling implementation.

Good use of -print0 with xargs -0 for safe filename handling, defensive checks for setfacl availability, and informative sticky-bit detection. The path validation guards against accidental operations.


305-345: Well-structured matrix generation.

Good validation of the build config directory existence and handling of empty releases array. The JSON construction using jq -s is appropriate.


1084-1097: Double rsync may be redundant.

The first rsync (line 1088-1090) transfers files, then the second rsync (line 1095-1097) does the same transfer plus --delete. The second command alone would accomplish both tasks. Is the first sync intentional (e.g., ensuring files exist before deletion pass)?


610-615: Comprehensive security hardening.

Excellent SSRF prevention with URL scheme validation, hostname format validation with regex, path traversal checks, port range validation, and username format validation. The timeout and error handling for curl calls is well implemented.

Also applies to: 728-733, 790-800


509-581: Well-implemented cleanup with safety guards.

Good validation of the cleanup flag, path safety checks, use of -print0/xargs -0 for safe filename handling, and verification that deletion succeeded. The additional check for files vs directories adds defense in depth.

- Add comprehensive input validation for all paths and parameters
- Implement SSRF prevention for API calls
- Add format validation for hostnames, usernames, and ports
- Enhance NetBox API error handling and validation
- Improve permission management with safety checks
- Add dry-run support for sync operations
- Strengthen repository path validation throughout
- Enhance security for beta repository operations

Signed-off-by: Igor Pecovnik <igor@armbian.com>
@igorpecovnik igorpecovnik merged commit 6cf2989 into main Jan 2, 2026
1 check was pending
@igorpecovnik igorpecovnik deleted the repohandling branch January 2, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant