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
10 changes: 10 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Bash(gh pr:*)",
"WebFetch(domain:github.com)",
"WebFetch(domain:raw.githubusercontent.com)",
"Bash(curl:*)"
]
Comment on lines +3 to +8
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Check whether local Claude settings are intentionally tracked:"
fd -H '^settings\.local\.json$' .claude || true

echo "Check ignore rules for local Claude settings:"
if [ -f .gitignore ]; then
  rg -n '\.claude|settings\.local\.json' .gitignore || true
fi

echo "Find references to broad permissions:"
rg -n 'Bash\(gh pr:\*\)|Bash\(curl:\*\)|settings\.local\.json' -g '!**/.git/**'

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 251


Add .claude/settings.local.json to .gitignore or narrow the overly permissive permissions.

The file is currently tracked in the repository with broad wildcard shell access (Bash(gh pr:*) and Bash(curl:*)). If this is a local-only setting, it should be ignored. If intentionally shared, these permissions exceed least privilege and create unnecessary exfiltration/misuse risk—narrow them to specific commands instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/settings.local.json around lines 3 - 8, The tracked
.claude/settings.local.json contains an overly-permissive "allow" array (notably
"Bash(gh pr:*)" and "Bash(curl:*)") which should not be committed; either add
.claude/settings.local.json to .gitignore and remove it from the repo, or
tighten the "allow" entries to the minimum necessary specific commands (replace
wildcards like "Bash(gh pr:*)" and "Bash(curl:*)" with explicit command strings
or restrict domains), then commit the narrowed settings so the file can remain
shared safely.

}
}
2 changes: 1 addition & 1 deletion .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
],
"commitMessageAction": "Renovate: Update",
"constraints": {
"go": "1.25"
"go": "1.26"
},
"dependencyDashboardOSVVulnerabilitySummary": "all",
"osvVulnerabilityAlerts": true,
Expand Down
15 changes: 6 additions & 9 deletions .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ name: Checks
permissions:
checks: write
contents: read
pull-requests: read
jobs:
checks:
name: Checks
Expand All @@ -30,7 +29,7 @@ jobs:
uses: actions/setup-go@v6
with:
check-latest: true
go-version: 1.25.5
go-version: 1.26.1
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v9
with:
Expand All @@ -42,13 +41,11 @@ jobs:
- name: Dependency Licenses Review
run: make check-dependency-licenses
- name: Check for spelling errors
uses: reviewdog/action-misspell@v1
with:
exclude: ./vendor/*
fail_on_error: true
github_token: ${{ secrets.GITHUB_TOKEN }}
ignore: importas
reporter: github-check
uses: crate-ci/typos@v1
env:
CLICOLOR: "1"
- name: Delete typos binary
run: rm typos
Comment on lines +47 to +48
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "checks.yaml" -path "*/.github/workflows/*" -type f

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 108


🏁 Script executed:

cat -n .github/workflows/checks.yaml | head -60

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 2192


🌐 Web query:

crate-ci/typos@v1 github action binary extraction behavior

💡 Result:

crate-ci/typos@v1 is a composite action that runs action/entrypoint.sh and (if needed) downloads a prebuilt typos release asset and extracts just the binary into the install directory. [1] [2]

Binary install / extraction behavior:

  • Install location: the action sets INSTALL_DIR: ., so it installs to the workspace root and expects to run ./typos (i.e., COMMAND="${INSTALL_DIR}/typos"). [2] [1]
  • Skips download if already present: it only downloads when ${COMMAND} is not executable ([[ ! -x ${COMMAND} ]]). [1]
  • Which asset it downloads: it picks an OS/arch-specific asset name and downloads from GitHub Releases via wget (Linux uses *-unknown-linux-musl.tar.gz, macOS uses *.tar.gz, Windows uses *.zip). [1]
  • Extraction:
    • For tar.gz: tar -xzvf "${FILE_NAME}" -C ${INSTALL_DIR} ./${CMD_NAME} → extracts only ./typos from the archive into INSTALL_DIR (not the full archive contents). [1]
    • For zip (Windows): unzip -o "${FILE_NAME}" -d ${INSTALL_DIR} typos.exe → extracts only typos.exe, overwriting if present (-o). [1]
  • Pinned typos version (inside the script): the script currently hardcodes VERSION=1.44.0, so @v1 will keep installing that until the tag/script is updated. [1]

Sources: [1] [2]


Make this cleanup step non-failing.

The crate-ci/typos@v1 action extracts a ./typos binary to the workspace root, but using rm typos without the -f flag causes the step to fail if the binary is ever missing. Use rm -f typos to make it non-failing and protect against future action changes.

Suggested fix
       - name: Delete typos binary
-        run: rm typos
+        run: rm -f typos
📝 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.

Suggested change
- name: Delete typos binary
run: rm typos
- name: Delete typos binary
run: rm -f typos
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/checks.yaml around lines 47 - 48, The "Delete typos
binary" cleanup step uses a brittle command `rm typos` that will fail if the
`typos` binary is missing; update that command to be non-failing by using the
force flag (replace `rm typos` with `rm -f typos`) so the step will not error
when the file is absent, ensuring the workflow remains robust against changes in
the crate-ci/typos action.

- name: Check if source code files have license header
run: make check-addlicense
- name: REUSE Compliance Check
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
uses: actions/setup-go@v6
with:
check-latest: true
go-version: 1.25.5
go-version: 1.26.1
- name: Build all binaries
run: make build-all
code_coverage:
Expand All @@ -45,7 +45,7 @@ jobs:
- name: Check out code
uses: actions/checkout@v6
- name: Post coverage report
uses: fgrosse/go-coverage-report@v1.2.0
uses: fgrosse/go-coverage-report@v1.3.0
with:
coverage-artifact-name: code-coverage
coverage-file-name: cover.out
Expand All @@ -65,11 +65,11 @@ jobs:
uses: actions/setup-go@v6
with:
check-latest: true
go-version: 1.25.5
go-version: 1.26.1
- name: Run tests and generate coverage report
run: make build/cover.out
- name: Archive code coverage results
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: code-coverage
path: build/cover.out
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
uses: actions/setup-go@v6
with:
check-latest: true
go-version: 1.25.5
go-version: 1.26.1
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/container-registry-ghcr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
################################################################################
# This file is AUTOGENERATED with <https://github.com/sapcc/go-makefile-maker> #
# Edit Makefile.maker.yaml instead. #
################################################################################

# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company
# SPDX-License-Identifier: Apache-2.0

name: Container Registry GHCR
"on":
push:
branches:
- master
workflow_dispatch: {}
Comment on lines +11 to +14
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "container-registry-ghcr.yaml" -type f

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 125


🏁 Script executed:

cat -n ./.github/workflows/container-registry-ghcr.yaml

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 2570


🏁 Script executed:

# Let me search for any documentation or comments about how docker/metadata-action behaves with semver
web_search "docker/metadata-action type=semver git tags trigger"

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 127


🏁 Script executed:

web_search "docker metadata-action semver pattern git tag ref"

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 127


🌐 Web query:

docker/metadata-action type=semver how git tags trigger works

💡 Result:

How Git tags trigger docker/metadata-action with type=semver

  1. A GitHub Actions workflow must be triggered by a tag push
  • Your workflow needs a tag trigger, e.g.:
    on:
      push:
        tags:
          - 'v*'
  • The workflow run happens when the tag is pushed to the remote (not when it’s only created locally). GitHub exposes the pushed tag as GITHUB_REF=refs/tags/<tag>. [3]
  1. type=semver runs (by design) on “push tag” events and parses the tag
  • docker/metadata-action says type=semver “will be used on a push tag event” and it requires a valid SemVer Git tag (unless you provide a custom value=). [1]
  • It parses the tag into template fields you can use in pattern=..., such as:
    • {{raw}} (the tag text, e.g. v1.2.3)
    • {{version}} (e.g. 1.2.3, including prerelease like 2.0.8-beta.67)
    • {{major}}, {{minor}}, {{patch}} [1]
  1. What tags it generates (common setup)
  • Example config:
    - uses: docker/metadata-action@v6
      with:
        tags: |
          type=semver,pattern={{version}}
          type=semver,pattern={{major}}.{{minor}}
          type=semver,pattern={{major}}
  • If you push Git tag v1.2.3, it will generate Docker tags like 1.2.3, 1.2, 1 (based on your patterns). [1]
  1. Where the “version” comes from
  • On a tag-triggered run, type=semver uses the Git tag that triggered the workflow as its input (unless overridden with value=). [1]
  • If you omit pattern, docs/discussions indicate it effectively defaults to using the tag value (so you normally set pattern to control what gets emitted). [2]

Mental model: git tag v1.2.3 && git push origin v1.2.3 → workflow triggers on refs/tags/v1.2.3metadata-action parses v1.2.3 as SemVer → outputs the Docker tags you described with pattern=. [1][3]


type=semver tags never run with the current trigger.

This workflow only fires on pushes to master, but the semver templates are populated from tag refs. As written, pushing v1.2.3 will not publish v1.2.3, v1.2, or v1 images. Either add a push.tags trigger or drop the semver tag templates.

Suggested fix
 "on":
   push:
     branches:
       - master
+    tags:
+      - 'v*'
   workflow_dispatch: {}

Also applies to: 36-44

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/container-registry-ghcr.yaml around lines 11 - 14, The
workflow currently triggers only on pushes to branch master so semver tag-based
builds (the type=semver tag templates used for image tags) never run; update the
workflow trigger by adding a push.tags pattern (e.g., add a push: tags: entry
that matches your semver tags) so that pushing a tag like v1.2.3 will invoke the
job and produce the semver-derived tags, or alternatively remove the semver tag
templates if you do not intend to trigger on tags; look for the push: block and
the workflow_dispatch: {} entry to add the tags: configuration or remove the
semver templates accordingly.

permissions:
contents: read
packages: write
jobs:
build-and-push-image:
name: Push container to ghcr.io
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Log in to the Container registry
uses: docker/login-action@v4
with:
password: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io
username: ${{ github.actor }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository }}
tags: |
# https://github.com/docker/metadata-action#typeedge
type=edge
# https://github.com/docker/metadata-action#latest-tag
type=raw,value=latest,enable={{is_default_branch}}
# https://github.com/docker/metadata-action#typesemver
type=semver,pattern={{raw}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}}
# https://github.com/docker/metadata-action#typesha
type=sha,format=long
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build and push Docker image
uses: docker/build-push-action@v7
Comment on lines +23 to +52
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/container-registry-ghcr.yaml | head -60

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 2570


🏁 Script executed:

fd Makefile.maker.yaml

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 96


🏁 Script executed:

cat -n Makefile.maker.yaml

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 1392


🏁 Script executed:

web_search
Query: go-makefile-maker action pinning SHA github workflows

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 172


🌐 Web query:

go-makefile-maker github actions pinning commit SHA security

💡 Result:

If you use go-makefile-maker in GitHub Actions, treat it like any other third‑party dependency and aim for immutable pinning:

  • Pin GitHub Actions to a full 40‑char commit SHA (not @main, not @v1). GitHub explicitly recommends this as the primary way to make an action reference immutable and reduce supply‑chain risk (tags can be moved) [1]. GitHub org/repo policies can also require SHA pinning so workflows fail if actions aren’t pinned [2], and GitHub added policy support specifically for SHA pinning/blocking [3].
  • If you’re installing the tool as a Go module (typical for go-makefile-maker), also pin the module version (tag or Go pseudo‑version) instead of @latest, e.g. go install github.com/SuperSandro2000/go-makefile-maker@<version> [4].

Example (what “pin to SHA” looks like):

- uses: OWNER/REPO@0123456789abcdef0123456789abcdef01234567  # v1.2.3

Sources

  1. GitHub Docs – Security hardening for GitHub Actions (pin actions to full-length commit SHA) [1]
  2. GitHub Docs – repo/org settings: Require actions to be pinned to a full-length commit SHA [2]
  3. GitHub Changelog – Actions policy supports SHA pinning/blocking [3]
  4. pkg.go.dev – github.com/SuperSandro2000/go-makefile-maker module reference [4]

Pin GitHub Actions to commit SHAs instead of floating version tags.

This workflow pushes container images with packages: write permission, but all uses: references use floating major version tags (@v4, @v6, @v7). If an upstream action is retagged or compromised, a malicious image could be published from this repository. However, since .github/workflows/container-registry-ghcr.yaml is autogenerated by go-makefile-maker (see file header), manually editing this file will not persist. You need to either:

  • Update Makefile.maker.yaml to configure action pinning if go-makefile-maker supports it, or
  • Create a non-autogenerated workflow file with actions pinned to full commit SHAs (GitHub's recommended security hardening)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/container-registry-ghcr.yaml around lines 23 - 52, The
workflow currently pins actions with floating tags (actions/checkout@v6,
docker/login-action@v4, docker/metadata-action@v6, docker/setup-qemu-action@v4,
docker/setup-buildx-action@v4, docker/build-push-action@v7) which is unsafe; fix
by either updating the generator configuration (Makefile.maker.yaml /
go-makefile-maker) to emit workflows that pin each uses: reference to a full
commit SHA, or stop regenerating this file and add a new non-autogenerated
workflow that replaces each floating tag with the corresponding
action@<full-commit-sha> (for actions/checkout, docker/login-action,
docker/metadata-action, docker/setup-qemu-action, docker/setup-buildx-action,
docker/build-push-action) and retain the existing inputs (password, registry,
username, images, tags, ids) and permissions; ensure the autogenerated file is
not overwritten (or adjust generator) so the pinned SHAs persist.

with:
context: .
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
15 changes: 13 additions & 2 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ linters:
- ineffassign
- intrange
- iotamixing
- misspell
- modernize
- nilerr
- nolintlint
Expand Down Expand Up @@ -137,8 +136,20 @@ linters:
excludes:
# gosec wants us to set a short ReadHeaderTimeout to avoid Slowloris attacks, but doing so would expose us to Keep-Alive race conditions (see https://iximiuz.com/en/posts/reverse-proxy-http-keep-alive-and-502s/
- G112
# if we put a password or token into a serialized payload, guess what, we probably did that on purpose
- G117
# this triggers on net/http.Request.ParseForm() and its callers, e.g. net/http.Request.FormValue(), complaining about potential memory exhaustion from unbounded form parsing;
# but that is incorrect, ParseForm() by default never parses more than 10 MiB for this specific reason
- G120
# created file permissions are restricted by umask if necessary
- G306
# the following lints cause false-positives in many repositories, should be fixed with the next release. (see https://github.com/securego/gosec/issues/1500)
- G701
- G702
- G703
- G704
- G705
- G706
Comment on lines +139 to +152
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the .golangci.yaml file
find . -name ".golangci.yaml" -o -name ".golangci.yml" | head -20

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 93


🏁 Script executed:

# Read the .golangci.yaml file with context around lines 139-152
wc -l .golangci.yaml

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 95


🏁 Script executed:

# Show the relevant section with surrounding context
head -160 .golangci.yaml | tail -30

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 1404


🏁 Script executed:

# Check if there are any `#nosec` or //nolint:gosec annotations in the codebase currently
rg "#nosec|//nolint.*gosec" --type go | head -20

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 59


🏁 Script executed:

# Search for specific code patterns that would match the excluded rules
# G120: net/http.Request.ParseForm() or FormValue()
rg "ParseForm|FormValue" --type go -n

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 59


🏁 Script executed:

# Search for patterns related to G117 (hardcoded credentials) and G306 (file permissions)
rg "os.Create|os.OpenFile|NewFile|chmod" --type go -n | head -30

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 59


🏁 Script executed:

# Check if golangci-lint documentation is available locally
find . -name "*.md" -type f | xargs grep -l "gosec\|excludes" 2>/dev/null | head -5

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 59


🏁 Script executed:

# Get an overview of the codebase structure
find . -type f -name "*.go" | wc -l
ls -la | head -20

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 1281


🏁 Script executed:

# Check git history or comments to understand why these rules were excluded
git log --oneline -n 20 -- .golangci.yaml 2>/dev/null | head -10

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 106


🏁 Script executed:

# Search for any code that might involve password/token handling (related to G117)
rg "password|token|secret|credential" --type go -i -l | head -10

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 274


🏁 Script executed:

# Check the content of files that mention password/token/secret
cat cloudprofilesync/source.go | head -100

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 2352


🏁 Script executed:

# Check the controller files for password/token handling
rg "password|token|secret" --type go -B 2 -A 2

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 4930


🏁 Script executed:

# Check if there are any comments in the code mentioning gosec or nosec
rg "gosec|nosec|nolint" --type go | head -20

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 59


🏁 Script executed:

# Look for the issue mentioned in the comment (https://github.com/securego/gosec/issues/1500)
# to understand the context of G701-G706 exclusions
rg "G701|G702|G703|G704|G705|G706" .

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 59


🏁 Script executed:

# Verify the exact usage of OCIParams to understand if password serialization is truly necessary
rg "OCIParams" --type go -B 2 -A 2

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 1341


🏁 Script executed:

# Check if there's any marshaling/serialization of OCIParams or similar structures
rg "json\.Marshal|json\.Unmarshal|MarshalJSON|UnmarshalJSON" --type go

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 914


🏁 Script executed:

# Check if OCIParams is ever marshaled or serialized
rg "NewOCI" --type go -A 5 | head -40

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 1344


🏁 Script executed:

# Look at the full controller code to see if the password ends up in serialized form
sed -n '1,100p' controllers/managedcloudprofile_controller.go

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 3670


🏁 Script executed:

# Check if OCIParams json tags are actually used anywhere (marshaled/unmarshaled)
rg "OCIParams" --type go -A 10 -B 2 | grep -A 10 -B 2 "Marshal\|Unmarshal"

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 59


🏁 Script executed:

# Verify the complete picture: is OCIParams ever serialized?
ast-grep --pattern 'json.Marshal($$_OCIParams)' || true
ast-grep --pattern 'json.Unmarshal($$_, $$_OCIParams)' || true

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 59


🏁 Script executed:

# Check if there are any http.Request.ParseForm or FormValue calls
rg "ParseForm|FormValue" --type go -n

Repository: cobaltcore-dev/cloud-profile-sync

Length of output: 59


Remove unnecessary gosec exclusions or narrow their scope.

The settings.gosec.excludes disables these rules globally, but many aren't actually needed: there are no ParseForm() or FormValue() calls (G120), no file permission operations (G306), and OCIParams is never marshaled to JSON despite the password field having json tags (undermining the G117 justification). Only G701–G706 have clear justification as known upstream false-positives. Remove or localize G120 and G306; for the rest, consider scoping suppressions using local #nosec annotations on the specific lines that genuinely need them, or wait for the upstream gosec fixes referenced in the configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.golangci.yaml around lines 139 - 152, The global gosec excludes list is too
broad—remove or narrow G117, G120 and G306 from settings.gosec.excludes and
instead apply targeted suppressions: remove G120 and G306 entirely since there
are no net/http.ParseForm()/FormValue() calls and no file-permission ops; for
G117, remove the global exclusion and add a local `#nosec` annotation only where
the OCIParams struct (or any other struct with a password/json tag) is actually
serialized, or delete it if OCIParams is never marshaled; keep the upstream
false-positive group (G701–G706) if desired but prefer local `#nosec` comments on
the specific functions/types that need them rather than a global disable.

govet:
disable:
- fieldalignment
Expand All @@ -147,7 +158,7 @@ linters:
require-specific: true
modernize:
disable:
# omitzero requires removing omitempty tags in kubernetes api struct types which are nested, which is intepreted by controller-gen and breaks the CRDs.
# omitzero requires removing omitempty tags in kubernetes api struct types which are nested, which is interpreted by controller-gen and breaks the CRDs.
- omitzero
perfsprint:
# modernize generates nicer fix code
Expand Down
1 change: 1 addition & 0 deletions .license-scan-overrides.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
{"name": "github.com/mattn/go-localereader", "licenceType": "MIT"}
{"name": "github.com/miekg/dns", "licenceType": "BSD-3-Clause"}
{"name": "github.com/pashagolub/pgxmock/v4", "licenceType": "BSD-3-Clause"}
{"name": "github.com/pashagolub/pgxmock/v5", "licenceType": "BSD-3-Clause"}
{"name": "github.com/spdx/tools-golang", "licenceTextOverrideFile": "vendor/github.com/spdx/tools-golang/LICENSE.code"}
{"name": "github.com/xeipuuv/gojsonpointer", "licenceType": "Apache-2.0"}
{"name": "github.com/xeipuuv/gojsonreference", "licenceType": "Apache-2.0"}
Expand Down
10 changes: 10 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: 2025 SAP SE
#
# SPDX-License-Identifier: Apache-2.0

[default.extend-words]

[files]
extend-exclude = [
"go.mod",
]
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# SPDX-License-Identifier: Apache-2.0

# Build the manager binary
FROM golang:1.25-alpine AS builder
FROM golang:1.26-alpine AS builder

WORKDIR /workspace
ENV GOTOOLCHAIN=local
Expand Down
17 changes: 13 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ install-golangci-lint: FORCE
@if ! hash golangci-lint 2>/dev/null; then printf "\e[1;36m>> Installing golangci-lint (this may take a while)...\e[0m\n"; go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest; fi

install-shellcheck: FORCE
@if ! hash shellcheck 2>/dev/null; then printf "\e[1;36m>> Installing shellcheck...\e[0m\n"; SHELLCHECK_ARCH=$(shell uname -m); if [[ "$$SHELLCHECK_ARCH" == "arm64" ]]; then SHELLCHECK_ARCH=aarch64; fi; SHELLCHECK_OS=$(shell uname -s | tr '[:upper:]' '[:lower:]'); SHELLCHECK_VERSION="stable"; if command -v curl >/dev/null 2>&1; then GET="curl -sLo-"; elif command -v wget >/dev/null 2>&1; then GET="wget -O-"; else echo "Didn't find curl or wget to download shellcheck"; exit 2; fi; $$GET "https://github.com/koalaman/shellcheck/releases/download/$$SHELLCHECK_VERSION/shellcheck-$$SHELLCHECK_VERSION.$$SHELLCHECK_OS.$$SHELLCHECK_ARCH.tar.xz" | tar -Jxf -; BIN=$$(go env GOBIN); if [[ -z $$BIN ]]; then BIN=$$(go env GOPATH)/bin; fi; install -Dm755 shellcheck-$$SHELLCHECK_VERSION/shellcheck -t "$$BIN"; rm -rf shellcheck-$$SHELLCHECK_VERSION; fi
@set -eou pipefail; if ! hash shellcheck 2>/dev/null; then printf "\e[1;36m>> Installing shellcheck...\e[0m\n"; SHELLCHECK_ARCH=$$(uname -m); if [[ "$$SHELLCHECK_ARCH" == "arm64" ]]; then SHELLCHECK_ARCH=aarch64; fi; SHELLCHECK_OS=$$(uname -s | tr '[:upper:]' '[:lower:]'); SHELLCHECK_VERSION="stable"; if command -v curl >/dev/null 2>&1; then GET="curl -sLo-"; elif command -v wget >/dev/null 2>&1; then GET="wget -O-"; else echo "Didn't find curl or wget to download shellcheck"; exit 2; fi; $$GET "https://github.com/koalaman/shellcheck/releases/download/$$SHELLCHECK_VERSION/shellcheck-$$SHELLCHECK_VERSION.$$SHELLCHECK_OS.$$SHELLCHECK_ARCH.tar.xz" | tar -Jxf -; BIN=$$(go env GOBIN); if [[ -z $$BIN ]]; then BIN=$$(go env GOPATH)/bin; fi; install -Dm755 shellcheck-$$SHELLCHECK_VERSION/shellcheck -t "$$BIN"; rm -rf shellcheck-$$SHELLCHECK_VERSION; fi

install-typos: FORCE
@set -eou pipefail; if ! hash typos 2>/dev/null; then printf "\e[1;36m>> Installing typos...\e[0m\n"; TYPOS_ARCH=$$(uname -m); if [[ "$$TYPOS_ARCH" == "arm64" ]]; then TYPOS_ARCH=aarch64; fi; if command -v curl >/dev/null 2>&1; then GET="curl $${GITHUB_TOKEN:+" -u \":$$GITHUB_TOKEN\""} -sLo-"; elif command -v wget >/dev/null 2>&1; then GET="wget $${GITHUB_TOKEN:+" --password \"$$GITHUB_TOKEN\""} -O-"; else echo "Didn't find curl or wget to download typos"; exit 2; fi; if command -v gh >/dev/null; then TYPOS_GET_RELEASE_JSON="gh api /repos/crate-ci/typos/releases"; else TYPOS_GET_RELEASE_JSON="$$GET https://api.github.com/repos/crate-ci/typos/releases"; fi; TYPOS_VERSION=$$($$TYPOS_GET_RELEASE_JSON | jq -r '.[0].name' ); if [[ $(UNAME_S) == Darwin ]]; then TYPOS_FILE="typos-$$TYPOS_VERSION-$$TYPOS_ARCH-apple-darwin.tar.gz"; elif [[ $(UNAME_S) == Linux ]]; then TYPOS_FILE="typos-$$TYPOS_VERSION-$$TYPOS_ARCH-unknown-linux-musl.tar.gz"; fi; mkdir -p typos; $$GET ""https://github.com/crate-ci/typos/releases/download/$$TYPOS_VERSION/$$TYPOS_FILE"" | tar -C typos -zxf -; BIN=$$(go env GOBIN); if [[ -z $$BIN ]]; then BIN=$$(go env GOPATH)/bin; fi; install -Dm755 typos/typos -t "$$BIN"; rm -rf typos/; fi

install-go-licence-detector: FORCE
@if ! hash go-licence-detector 2>/dev/null; then printf "\e[1;36m>> Installing go-licence-detector (this may take a while)...\e[0m\n"; go install go.elastic.co/go-licence-detector@latest; fi
Expand All @@ -54,7 +57,7 @@ install-addlicense: FORCE
install-reuse: FORCE
@if ! hash reuse 2>/dev/null; then if ! hash pipx 2>/dev/null; then printf "\e[1;31m>> You are required to manually intervene to install reuse as go-makefile-maker cannot automatically resolve installing reuse on all setups.\e[0m\n"; printf "\e[1;31m>> The preferred way for go-makefile-maker to install python tools after nix-shell is pipx which could not be found. Either install pipx using your package manager or install reuse using your package manager if at least version 6 is available.\e[0m\n"; printf "\e[1;31m>> As your Python was likely installed by your package manager, just doing pip install --user sadly does no longer work as pip issues a warning about breaking your system. Generally running --break-system-packages with --user is safe to do but you should only run this command if you can resolve issues with it yourself: pip3 install --user --break-system-packages reuse\e[0m\n"; else printf "\e[1;36m>> Installing reuse...\e[0m\n"; pipx install reuse; fi; fi

prepare-static-check: FORCE install-goimports install-golangci-lint install-shellcheck install-go-licence-detector install-addlicense install-reuse
prepare-static-check: FORCE install-goimports install-golangci-lint install-shellcheck install-typos install-go-licence-detector install-addlicense install-reuse
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

run-typos is still missing from make static-check.

prepare-static-check now installs typos, but the standard static-check chain never invokes this new target. That leaves local make static-check out of sync with CI and pushes typo failures to PR time instead of dev time.

Suggested fix
-__static-check: FORCE run-shellcheck run-golangci-lint check-dependency-licenses check-license-headers
+__static-check: FORCE run-shellcheck run-typos run-golangci-lint check-dependency-licenses check-license-headers

Also applies to: 122-124

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` at line 60, The static-check flow is missing the run-typos step:
update the Makefile so the existing prepare-static-check (which installs typos)
is actually exercised by adding the run-typos target into the static-check chain
(or add run-typos as a prerequisite of the static-check target); specifically
modify the static-check target to depend on or invoke run-typos (referencing
targets prepare-static-check, static-check, and run-typos) so local make
static-check matches CI and catches typos earlier.


install-controller-gen: FORCE
@if ! hash controller-gen 2>/dev/null; then printf "\e[1;36m>> Installing controller-gen (this may take a while)...\e[0m\n"; go install sigs.k8s.io/controller-tools/cmd/controller-gen@latest; fi
Expand All @@ -76,7 +79,7 @@ build/cloud-profile-sync: FORCE generate
env $(GO_BUILDENV) go build $(GO_BUILDFLAGS) -ldflags '-s -w $(GO_LDFLAGS)' -o build/cloud-profile-sync .

DESTDIR =
ifeq ($(shell uname -s),Darwin)
ifeq ($(UNAME_S),Darwin)
PREFIX = /usr/local
else
PREFIX = /usr
Expand Down Expand Up @@ -116,6 +119,10 @@ run-shellcheck: FORCE install-shellcheck
@printf "\e[1;36m>> shellcheck\e[0m\n"
@find . -type f \( -name '*.bash' -o -name '*.ksh' -o -name '*.zsh' -o -name '*.sh' -o -name '*.shlib' \) -exec shellcheck {} +

run-typos: FORCE install-typos
@printf "\e[1;36m>> typos\e[0m\n"
@typos

build/cover.out: FORCE generate install-setup-envtest | build
@printf "\e[1;36m>> Running tests\e[0m\n"
KUBEBUILDER_ASSETS=$$(setup-envtest use 1.34 -p path) go run github.com/onsi/ginkgo/v2/ginkgo run --randomize-all -output-dir=build $(GO_BUILDFLAGS) -ldflags '-s -w $(GO_LDFLAGS)' -covermode=count -coverpkg=$(subst $(space),$(comma),$(GO_COVERPKGS)) $(GO_TESTFLAGS) $(GO_TESTPKGS)
Expand Down Expand Up @@ -151,7 +158,7 @@ license-headers: FORCE install-addlicense install-reuse
@printf "\e[1;36m>> addlicense (for license headers on source code files)\e[0m\n"
@printf "%s\0" $(patsubst $(shell awk '$$1 == "module" {print $$2}' go.mod)%,.%/*.go,$(shell go list ./...)) | $(XARGS) -0 -I{} bash -c 'year="$$(grep 'Copyright' {} | head -n1 | grep -E -o '"'"'[0-9]{4}(-[0-9]{4})?'"'"')"; if [[ -z "$$year" ]]; then year=$$(date +%Y); fi; gawk -i inplace '"'"'{if (display) {print} else {!/^\/\*/ && !/^\*/}}; {if (!display && $$0 ~ /^(package |$$)/) {display=1} else { }}'"'"' {}; addlicense -c "SAP SE or an SAP affiliate company" -s=only -y "$$year" -- {}; $(SED) -i '"'"'1s+// Copyright +// SPDX-FileCopyrightText: +'"'"' {}; '
@printf "\e[1;36m>> reuse annotate (for license headers on other files)\e[0m\n"
@reuse lint -j | jq -r '.non_compliant.missing_licensing_info[]' | grep -vw vendor | $(XARGS) reuse annotate -c 'SAP SE or an SAP affiliate company' -l Apache-2.0 --skip-unrecognised
@reuse lint -j | jq -r '.non_compliant.missing_licensing_info[]' | sed '/\<vendor\>/d' | $(XARGS) reuse annotate -c 'SAP SE or an SAP affiliate company' -l Apache-2.0 --skip-unrecognised
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reuse the $(SED) abstraction here.

This hardcodes sed again and bypasses the Darwin gsed override above. On macOS that reopens the portability problem you already accounted for, and \<vendor\> is exactly the sort of regex that differs.

Suggested fix
-	`@reuse` lint -j | jq -r '.non_compliant.missing_licensing_info[]' | sed '/\<vendor\>/d' | $(XARGS) reuse annotate -c 'SAP SE or an SAP affiliate company' -l Apache-2.0 --skip-unrecognised
+	`@reuse` lint -j | jq -r '.non_compliant.missing_licensing_info[]' | $(SED) '/\<vendor\>/d' | $(XARGS) reuse annotate -c 'SAP SE or an SAP affiliate company' -l Apache-2.0 --skip-unrecognised
📝 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.

Suggested change
@reuse lint -j | jq -r '.non_compliant.missing_licensing_info[]' | sed '/\<vendor\>/d' | $(XARGS) reuse annotate -c 'SAP SE or an SAP affiliate company' -l Apache-2.0 --skip-unrecognised
`@reuse` lint -j | jq -r '.non_compliant.missing_licensing_info[]' | $(SED) '/\<vendor\>/d' | $(XARGS) reuse annotate -c 'SAP SE or an SAP affiliate company' -l Apache-2.0 --skip-unrecognised
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` at line 161, Replace the hardcoded sed invocation in the Makefile
rule with the SED abstraction: change "sed '/\<vendor\>/d'" to "$(SED)
'/\<vendor\>/d'" so the Darwin override (gsed) is used; keep the existing regex
and quoting as-is and preserve the surrounding pipeline into $(XARGS) reuse
annotate -c 'SAP SE or an SAP affiliate company' -l Apache-2.0
--skip-unrecognised.

@printf "\e[1;36m>> reuse download --all\e[0m\n"
@reuse download --all
@printf "\e[1;35mPlease review the changes. If *.license files were generated, consider instructing go-makefile-maker to add overrides to REUSE.toml instead.\e[0m\n"
Expand Down Expand Up @@ -194,6 +201,7 @@ help: FORCE
@printf " \e[36minstall-goimports\e[0m Install goimports required by goimports/static-check\n"
@printf " \e[36minstall-golangci-lint\e[0m Install golangci-lint required by run-golangci-lint/static-check\n"
@printf " \e[36minstall-shellcheck\e[0m Install shellcheck required by run-shellcheck/static-check\n"
@printf " \e[36minstall-typos\e[0m Install typos required by run-typos/static-check\n"
@printf " \e[36minstall-go-licence-detector\e[0m Install-go-licence-detector required by check-dependency-licenses/static-check\n"
@printf " \e[36minstall-addlicense\e[0m Install addlicense required by check-license-headers/license-headers/static-check\n"
@printf " \e[36minstall-reuse\e[0m Install reuse required by license-headers/check-reuse\n"
Expand All @@ -211,6 +219,7 @@ help: FORCE
@printf " \e[36mgenerate\e[0m Generate code for Kubernetes CRDs and deepcopy.\n"
@printf " \e[36mrun-golangci-lint\e[0m Install and run golangci-lint. Installing is used in CI, but you should probably install golangci-lint using your package manager.\n"
@printf " \e[36mrun-shellcheck\e[0m Install and run shellcheck. Installing is used in CI, but you should probably install shellcheck using your package manager.\n"
@printf " \e[36mrun-typos\e[0m Check for spelling errors using typos.\n"
@printf " \e[36mbuild/cover.out\e[0m Run tests and generate coverage report.\n"
@printf " \e[36mbuild/cover.html\e[0m Generate an HTML file with source code annotations from the coverage report.\n"
@printf " \e[36mcheck-addlicense\e[0m Check license headers in all non-vendored .go files with addlicense.\n"
Expand Down
8 changes: 8 additions & 0 deletions Makefile.maker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ githubWorkflow:
defaultBranch: master
ci:
enabled: true
pushContainerToGhcr:
enabled: true
platforms: "linux/amd64"
tagStrategy:
- edge
- latest
- semver
- sha

renovate:
enabled: true
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/cobaltcore-dev/cloud-profile-sync

go 1.25
go 1.26

require (
github.com/blang/semver/v4 v4.0.0
Expand Down
3 changes: 2 additions & 1 deletion shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ mkShell {
nativeBuildInputs = [
addlicense
go-licence-detector
go_1_25
go_1_26
golangci-lint
gotools # goimports
kubernetes-controller-tools # controller-gen
renovate
reuse
setup-envtest
typos
# keep this line if you use bash
bashInteractive
];
Expand Down
Loading