Skip to content
Merged
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
94 changes: 77 additions & 17 deletions .github/workflows/ghcr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,79 @@ permissions:
contents: read
packages: write

env:
IMAGE: ghcr.io/${{ github.repository_owner }}/agent
Comment on lines +29 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## workflow excerpt"
if [ -f .github/workflows/ghcr.yml ]; then
  nl -ba .github/workflows/ghcr.yml | sed -n '1,160p'
else
  echo "missing .github/workflows/ghcr.yml"
fi

echo
echo "## action references and env usage"
rg -n "docker/build-push-action|image=|metadata-action|buildx|IMAGE:|github.repository_owner|load|platform" .github/workflows/ghcr.yml || true

echo
echo "## repository owner context from workflow"
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/ghcr.yml')
s=p.read_text() if p.exists() else ''
print('has repo_owner reference:', '${{ github.repository_owner }}' in s)
for i,line in enumerate(s.splitlines(),1):
    if 'github.repository_owner' in line:
        print(f'{i}: {line.strip()}')
PY

Repository: Portabase/agent

Length of output: 213


🌐 Web query:

Docker container registry repository name lowercase paths official documentation

💡 Result:

In the Docker ecosystem, repository names must be lowercase [1][2]. Official technical specifications and implementation references enforce that components within a repository path must consist of lowercase alphanumeric characters, optionally separated by periods, dashes, or underscores [3][4][2]. Key technical details regarding this requirement include: - Strict Enforcement: The Docker runtime and registry tools perform validation checks (such as the regex [a-z0-9]+(?:[._-][a-z0-9]+)* for path components) that explicitly reject uppercase letters [3][1][2]. If you attempt to use uppercase characters in a repository name, you will typically receive an error such as: invalid reference format: repository name must be lowercase [5][2]. - Scope of Constraint: This lowercase requirement applies to the repository path components (the namespace and the repository name itself) [2]. It does not apply to the hostname/domain portion of a reference, nor does it apply to image tags, which are case-sensitive and may contain uppercase letters [4][2]. - Rationale: This constraint is maintained for cross-platform compatibility, particularly to avoid conflicts on file systems that are case-insensitive, such as those often used in Windows or macOS environments [5]. References to these rules can be found in the distribution specification and the source code for Docker reference parsing [3][1][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## workflow excerpt"
if [ -f .github/workflows/ghcr.yml ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' .github/workflows/ghcr.yml | sed -n '1,160p'
else
  echo "missing .github/workflows/ghcr.yml"
fi

echo
echo "## relevant references / calls"
rg -n "docker/build-push-action|image=|metadata-action|buildx|IMAGE:|github\.repository_owner|load|platform" .github/workflows/ghcr.yml || true

echo
echo "## deterministic check of IMAGE expansion for Portabase"
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/ghcr.yml')
s=p.read_text() if p.exists() else ''
owner="Portabase"
image="ghcr.io/${owner}/agent"
print("IMAGE expression value:", "${owner}/agent")
print("expanded image:", image)
print("repository_path_components_lowercase:", image.split("://",1)[1].split("/",1)[0].split("/")[1:2])
PY

Repository: Portabase/agent

Length of output: 6018


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## repo metadata"
git rev-parse --show-toplevel
git remote -v || true
if [ -f .git/config ]; then
  sed -n '/\[remote/,$p' .git/config
else
  echo "no .git/config"
fi

echo
echo "## workflow variables using owner with sed fallback"
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/ghcr.yml')
text=p.read_text()
for i,line in enumerate(text.splitlines(),1):
    if 'github.repository_owner' in line or 'IMAGE:' in line:
        print(f"{i}: {line}")
PY

echo
echo "## reference lowercase normalization in workflow"
python3 - <<'PY'
value="Portabase/agent"
expected="portabase/agent"
print("owner expansion source value:", value)
print("required registry repository component:", expected)
print("current env value would produce:", f"ghcr.io/{value}/agent")
print("lowercase would produce:", f"ghcr.io/{expected}/agent")
print("mixed-case contains uppercase path segment:", any(part != part.lower() for part in [value]))
PY

Repository: Portabase/agent

Length of output: 1015


🌐 Web query:

ghcr.io Portabase/agent repository exists

💡 Result:

Yes, the ghcr.io/portabase/agent container repository exists. While the primary container image for the Portabase Agent is hosted on Docker Hub as portabase/agent [1][2], the Portabase organization also utilizes the GitHub Container Registry (ghcr.io) for other components, such as its Helm charts (e.g., ghcr.io/portabase/charts/portabase) [3] and has recently added workflows to push images to ghcr.io [4]. Documentation and internal references confirm the project uses the GitHub Container Registry for distributing components [3][5].

Citations:


Normalize (image) to lowercase before Buildx.

IMAGE resolves to ghcr.io/Portabase/agent; repository path components must be lowercase, so the docker/build-push-action image output can be rejected. Use a lowercased value before the Buildx step, e.g. ghcr.io/${{ github.repository_owner }}.image.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ghcr.yml around lines 29 - 30, Update the IMAGE
environment value in the workflow to normalize the repository owner to lowercase
before it is consumed by docker/build-push-action, ensuring the resulting GHCR
image reference uses lowercase path components.


jobs:
publish:
name: Build and push to GHCR
runs-on: ubuntu-latest
build:
name: Build ${{ matrix.platform }}
runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-latest' || 'ubuntu-24.04-arm' }}
strategy:
fail-fast: false
matrix:
platform: [ linux/amd64, linux/arm64 ]
steps:
- name: Prepare arch vars
id: prep
run: |
ARCH="${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}"
echo "arch=$ARCH" >> "$GITHUB_OUTPUT"

- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
fetch-depth: 1
Comment on lines 47 to +51

Copy link
Copy Markdown

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:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
sed -n '1,120p' .github/workflows/ghcr.yml 2>/dev/null || true

echo
echo "== workflow files mentioning persist-credentials / actions/checkout =="
rg -n "persist-credentials|actions/checkout@|actions/checkout:" .github/workflows || true

echo
echo "== Dockerfile candidates =="
git ls-files | rg '(^|/)Dockerfile$|(^|/).*Dockerfile.*$' || true

echo
echo "== possible COPY . patterns =="
rg -n "^\s*COPY\s+" .github/workflows || true
rg -n "^\s*Dockerfile|Dockerfile:" .github/workflows || true

echo
echo "== changed files stat =="
git diff --stat || true

Repository: Portabase/agent

Length of output: 4080


🌐 Web query:

actions/checkout v4 persist-credentials default token git credentials

💡 Result:

In actions/checkout v4, the persist-credentials input defaults to true [1][2]. When enabled, this option configures the authentication token (or SSH key) used for the checkout within the local git configuration [3][2]. This default behavior allows subsequent steps in the same job to run authenticated git commands (such as git push or git fetch) without needing to manually re-specify the token or credentials [3][4]. The action manages this by removing the token during post-job cleanup [3]. If you prefer to prevent the credentials from being added to the local git config, you can explicitly opt-out by setting persist-credentials: false in your workflow file [3][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== docker/Dockerfile excerpt =="
wc -l docker/Dockerfile
sed -n '1,220p' docker/Dockerfile

echo
echo "== search git directory handling =="
rg -n "^\s*COPY\s+(\.\s*$|\./\s*$|.*\b(\.git|dockerignore)\b)|EXCLUDE|\.gitignore|dockerignore|\.dockerignore" docker/Dockerfile ./.dockerignore .github/workflows/ghcr.yml 2>/dev/null || true

echo
echo "== repository gitignore/dockerignore candidates =="
git ls-files | rg '(^|/)(\.dockerignore|Dockerfile.*)|(^|/)(\..*ignore|.*ignore)$' || true

echo
echo "== current .git existence and action config =="
python3 - <<'PY'
from pathlib import Path
import hashlib, json
for path in [Path('.git'), Path('docker/.dockerignore')]:
    print(f"{path}: exists={path.exists()} type={path.stat().st_mode if path.exists() else None}")
if Path('.git').exists():
    print(".git is present in the repository root, so context: . would include it unless docker context rules ignore it.")
PY

Repository: Portabase/agent

Length of output: 5283


Do not persist the checkout token in the workspace.

actions/checkout@v4 persists secrets.GITHUB_TOKEN by default, and docker/build-push-action@v6 uses context: .. Since COPY . . in docker/Dockerfile builds from the repository root, any .git directory containing local git config includes that token in the build cache/image unless checked out with persist-credentials: false. The GHCR login still has the packages scope it needs.

Proposed fix
       - name: Checkout
         uses: actions/checkout@v4
         with:
           ref: ${{ inputs.ref }}
           fetch-depth: 1
+          persist-credentials: false
📝 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: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
fetch-depth: 1
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
fetch-depth: 1
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

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

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ghcr.yml around lines 47 - 51, Update the Checkout step
using actions/checkout@v4 to disable credential persistence by setting
persist-credentials to false, while preserving the existing ref and fetch-depth
settings so the GITHUB_TOKEN is not written into the workspace used by
docker/build-push-action.

Source: Linters/SAST tools


- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
file: ${{ inputs.dockerfile }}
platforms: ${{ matrix.platform }}
target: ${{ inputs.target }}
provenance: false
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=ghcr-${{ steps.prep.outputs.arch }}
cache-to: type=gha,mode=max,scope=ghcr-${{ steps.prep.outputs.arch }},ignore-error=true

- name: Export digest
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: |
mkdir -p /tmp/digests
touch "/tmp/digests/${DIGEST#sha256:}"

- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digest-${{ steps.prep.outputs.arch }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

merge:
name: Create multi-arch manifest
runs-on: ubuntu-latest
needs: build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digest-*
merge-multiple: true

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
Expand All @@ -51,22 +114,19 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/agent
images: ${{ env.IMAGE }}
tags: |
type=semver,pattern={{version}},value=${{ inputs.version }}
type=semver,pattern={{major}}.{{minor}},value=${{ inputs.version }}
type=semver,pattern={{major}},value=${{ inputs.version }}
type=raw,value=latest,enable=${{ inputs.add_latest }}
Comment on lines 118 to 122

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
fd -a 'ghcr\.ya?ml' .github/workflows || true
file="$(fd 'ghcr\.ya?ml' .github/workflows | head -n1 || true)"
if [ -n "$file" ]; then
  wc -l "$file"
  sed -n '1,180p' "$file" | cat -n
fi

echo "== search docker/metadata action usage/options =="
rg -n "metadata-action|flavor|add_latest|latest|tags:" .github/workflows

echo "== repo files likely related =="
git ls-files .github/workflows | sed -n '1,120p'

Repository: Portabase/agent

Length of output: 7146


🌐 Web query:

docker/metadata-action v5 flavor latest default semver tags auto latest

💡 Result:

In the Docker Metadata Action (v5 and later), the flavor input controls how the latest tag is generated through its latest attribute, which defaults to auto [1][2]. The auto setting means the action implicitly enables a latest tag when using specific tag types that support it, provided they are not superseded by other tag types with higher priority [3][4]. The tag types that implicitly trigger this latest tag behavior include type=ref (event=tag), type=semver, type=pep440, and type=match [1][3]. If you wish to change this behavior, you can explicitly configure the latest attribute within the flavor input: - latest=true: Forces the generation of a latest tag [1]. - latest=false: Disables the generation of an implicit latest tag [3][4]. Additionally, if you are using prefixes or suffixes within the flavor input and want them to apply to the implicitly generated latest tag, you must explicitly enable this by adding,onlatest=true to the prefix or suffix definition [4]. Example configuration: flavor: | latest=auto suffix=-my-suffix,onlatest=true

Citations:


🌐 Web query:

docker/metadata-action v5 latest tag semver patterns flavor latest=false

💡 Result:

In the docker/metadata-action v5, setting flavor: | latest=false prevents the action from automatically generating and adding the latest tag to your output [1][2]. By default, the flavor input is set to latest=auto [3][4]. In auto mode, the action automatically generates a latest tag for specific events and tag types, including type=semver [3][5][6]. Using latest=false explicitly opts out of this behavior [5][2]. For type=semver tags specifically, the action supports patterns to generate versions from Git tags [7][8]. You can define these patterns in the tags input using Handlebars-style expressions [7]: - {{version}}: Shorthand for {{major}}.{{minor}}.{{patch}} [7] - {{major}}: Major version identifier [7] - {{minor}}: Minor version identifier [7] - {{patch}}: Patch version identifier [7] Example usage combining these features: - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: my-image tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} flavor: | latest=false This configuration will generate semver-based tags without adding an implicit latest tag [1][2].

Citations:


Disable metadata-action’s implicit latest tag.

docker/metadata-action@v5 defaults to flavor.latest=auto, and type=semver tags emit latest, so inputs.add_latest: false still publishes latest. Add flavor: latest=false and keep the explicit conditional raw tag.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ghcr.yml around lines 118 - 122, Add the metadata-action
flavor configuration with latest disabled in the workflow step defining the
semver tags, while preserving the conditional raw latest tag controlled by
inputs.add_latest. Ensure inputs.add_latest: false produces no latest tag.


- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
file: ${{ inputs.dockerfile }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
target: ${{ inputs.target }}
provenance: false
cache-from: type=gha,scope=ghcr-build
cache-to: type=gha,mode=max,scope=ghcr-build,ignore-error=true
- name: Create and push manifest list
working-directory: /tmp/digests
run: |
docker buildx imagetools create \
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.IMAGE }}@sha256:%s ' *)

- name: Inspect
run: docker buildx imagetools inspect ${{ env.IMAGE }}:${{ inputs.version }}
Comment on lines +131 to +132

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
sed -n '1,180p' .github/workflows/ghcr.yml 2>/dev/null || true

echo "== docker metadata action usages =="
rg -n "docker/metadata-action|steps\.meta\.outputs|inputs=.*version|version\b" .github/workflows/ghcr.yml .github/workflows 2>/dev/null || true

echo "== changed file context =="
git diff -- .github/workflows/ghcr.yml | sed -n '1,220p' || true

Repository: Portabase/agent

Length of output: 6662


🌐 Web query:

docker/metadata-action v5 semver output {{version}} version output v1.2.3 normalized

💡 Result:

In the docker/metadata-action (v5 and later), the type=semver tag configuration allows you to format version tags using a Handlebars-based pattern [1][2]. When you use the pattern={{version}} expression with a Git tag input of v1.2.3, the action normalizes the output by removing the 'v' prefix, resulting in 1.2.3 [1][3]. The version expression is a shorthand for {{major}}.{{minor}}.{{patch}} [2][4]. The following table summarizes how different patterns affect a v1.2.3 Git tag input [1][3]: | Git tag | Pattern | Output | | --- | --- | --- | | v1.2.3 | {{raw}} | v1.2.3 | | v1.2.3 | {{version}} | 1.2.3 | | v1.2.3 | {{major}}.{{minor}} | 1.2 | | v1.2.3 | v{{major}} | v1 | | v1.2.3 | {{minor}} | 2 | | v1.2.3 | {{patch}} | 3 | This behavior applies when triggered by a push tag event or when a custom value is provided via the value attribute [1][5]. Pre-release versions (e.g., v2.0.8-beta.67) are handled similarly, though they are treated as specific versions and do not always generate shorthand aliases like {{major}}.{{minor}} in the same way as stable releases due to their frequent update nature [2][4].

Citations:


Inspect the metadata-generated version, not raw input.

With type=semver,pattern={{version}},value=${{ inputs.version }}, inputs like v1.2.3 generate the 1.2.3 tag; inspecting ${{ inputs.version }} tries a tag that may not exist. Use steps.meta.outputs.version.

Proposed fix
       - name: Inspect
-        run: docker buildx imagetools inspect ${{ env.IMAGE }}:${{ inputs.version }}
+        env:
+          VERSION: ${{ steps.meta.outputs.version }}
+        run: docker buildx imagetools inspect "${IMAGE}:${VERSION}"
📝 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: Inspect
run: docker buildx imagetools inspect ${{ env.IMAGE }}:${{ inputs.version }}
- name: Inspect
env:
VERSION: ${{ steps.meta.outputs.version }}
run: docker buildx imagetools inspect "${IMAGE}:${VERSION}"
🧰 Tools
🪛 zizmor (1.26.1)

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

(template-injection)


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

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ghcr.yml around lines 131 - 132, Update the “Inspect” step
to reference the metadata-generated version from steps.meta.outputs.version
instead of inputs.version, so Docker inspects the normalized semver tag produced
by the metadata step.

Loading