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
10 changes: 5 additions & 5 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ env:
on:
push:
branches:
- main
- development

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

Limit the Docker Hub token to the login step.

DOCKER_TOKEN is job-scoped at Line 20, so every action and shell step in the docker job receives the credential. The new development trigger increases the code paths that receive this secret.

Pass secrets.OPT_DOCKER_ACCESS_TOKEN only to docker/login-action, or define it as a step-scoped environment variable.

As per path instructions, workflows must avoid secret exposure and use least-privilege secret scope.

🤖 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/docker-publish.yml at line 11, Restrict the Docker Hub
credential currently exposed through the docker job’s DOCKER_TOKEN environment
to the docker/login-action step only. Pass secrets.OPT_DOCKER_ACCESS_TOKEN
directly to that action or define a step-scoped environment variable, and remove
the job-level secret scope while preserving the existing login behavior.

Source: Path instructions


🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add bounded timeouts to both publishing jobs.

The docker and deploy-dev jobs have no job-level timeout-minutes. A multi-platform build or gh api dispatch can remain active indefinitely when a runner or external service hangs.

Add bounded values appropriate to the build and deployment durations.

As per path instructions, workflows must define job timeouts.

Also applies to: 176-192

🤖 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/docker-publish.yml at line 11, Add job-level
timeout-minutes values to both the docker and deploy-dev jobs in the workflow,
choosing bounds appropriate for their build and deployment durations. Ensure
each publishing job has a finite timeout while preserving the existing job steps
and behavior.

Source: Path instructions


🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
file=".github/workflows/docker-publish.yml"

if ! rg -n -C 4 '^\s*permissions:' "$file"; then
  echo "No explicit permissions block found" >&2
  exit 1
fi

Repository: getoptimum/optimum-gateway

Length of output: 507


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
file=".github/workflows/docker-publish.yml"

printf '%s\n' '--- workflow header and triggers ---'
sed -n '1,45p' "$file"

printf '%s\n' '--- permissions, concurrency, timeouts, and jobs ---'
rg -n -C 3 '^[[:space:]]*(permissions|concurrency|timeout-minutes):|^[[:space:]]{2}[A-Za-z0-9_-]+:' "$file"

printf '%s\n' '--- complete job definitions around permissions and credentials ---'
sed -n '40,230p' "$file"

Repository: getoptimum/optimum-gateway

Length of output: 9464


Add workflow-level least-privilege permissions. docker and deploy-dev have no job-level permissions and currently inherit repository defaults for GITHUB_TOKEN, which may allow writes. Add permissions: contents: read at workflow scope. Keep write access in the narrowly scoped GitHub App tokens.

🤖 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/docker-publish.yml at line 11, Add workflow-level
permissions in the Docker publish workflow, setting GITHUB_TOKEN access to
contents: read. Preserve write access only through the existing narrowly scoped
GitHub App tokens used by the docker and deploy-dev jobs.

Source: Path instructions


🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
file=".github/workflows/docker-publish.yml"

if ! rg -n '^\s*concurrency:' "$file"; then
  echo "No workflow concurrency group found" >&2
  exit 1
fi

Repository: getoptimum/optimum-gateway

Length of output: 200


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
file=".github/workflows/docker-publish.yml"

printf '%s\n' '--- workflow structure ---'
ast-grep outline "$file" --lang yaml 2>/dev/null || true

printf '%s\n' '--- relevant declarations ---'
rg -n -C 3 '^(name:|on:|  [A-Za-z0-9_-]+:|permissions:|concurrency:|jobs:)|timeout-minutes|uses:|docker/login-action|docker/build-push-action|deploy-dev|GitHub App|permission-|DOCKER_TOKEN|git push|repository_dispatch|workflow_dispatch' "$file" || true

printf '%s\n' '--- numbered workflow sections ---'
sed -n '1,240p' "$file"

Repository: getoptimum/optimum-gateway

Length of output: 12763


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

path = Path(".github/workflows/docker-publish.yml")
text = path.read_text()

top_level = [
    line for line in text.splitlines()
    if line and not line.startswith((" ", "\t")) and re.match(r"^[A-Za-z0-9_-]+:", line)
]
print("top_level_keys:", [line.split(":", 1)[0] for line in top_level])
print("workflow_concurrency:", bool(re.search(r"(?m)^concurrency\s*:", text)))
print("docker_push_gitops_main:", bool(re.search(r"(?m)^\s*git push origin main\s*$", text)))
print("development_trigger:", bool(re.search(r"(?m)^\s*-\s*development\s*$", text)))
print("deploy_dev_dispatches:", "client_payload[digest]" in text and "dev-latest" in text)
print("docker_timeout:", bool(re.search(r"(?ms)^  docker:\n.*?^\s+timeout-minutes\s*:", text)))
print("deploy_dev_timeout:", bool(re.search(r"(?ms)^  deploy-dev:\n.*?^\s+timeout-minutes\s*:", text)))
PY

Repository: getoptimum/optimum-gateway

Length of output: 339


Add a shared workflow-level concurrency group

The workflow has no concurrency declaration. Concurrent runs can race on git push origin main and dispatch deployment digests out of order. Use one group for all publishing runs, not a group keyed by github.ref, because both branch and tag runs update the same GitOps branch. Set cancel-in-progress: false.

🤖 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/docker-publish.yml at line 11, Add a workflow-level
concurrency declaration in the Docker publishing workflow using one fixed shared
group for all runs, rather than keying it by github.ref, and set
cancel-in-progress to false so publishing runs queue without cancellation.

Source: Path instructions

tags:
- v*

Expand Down Expand Up @@ -173,20 +173,20 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: Generate token for infrastructure dispatch (main)
id: infra-dispatch-token-main
- name: Generate token for infrastructure dispatch (development)
id: infra-dispatch-token-dev
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
owner: getoptimum
repositories: infrastructure
Comment on lines +176 to 183

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:

#!/usr/bin/env bash
set -euo pipefail
curl -fsSL \
  "https://raw.githubusercontent.com/actions/create-github-app-token/29824e69f54612133e76f7eaac726eef6c875baf/action.yml" |
  rg -n 'permission-contents'

Repository: getoptimum/optimum-gateway

Length of output: 190


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- workflow structure and token references ---'
rg -n -C 5 \
  'create-github-app-token|infra-dispatch-token|repository_dispatch|DOCKER_TOKEN|permissions:|concurrency:|timeout-minutes|uses:' \
  .github/workflows/docker-publish.yml

printf '%s\n' '--- pinned action metadata ---'
curl -fsSL \
  'https://raw.githubusercontent.com/actions/create-github-app-token/29824e69f54612133e76f7eaac726eef6c875baf/action.yml' |
  sed -n '1,180p'

Repository: getoptimum/optimum-gateway

Length of output: 16818


🌐 Web query:

site:github.com/actions/create-github-app-token README permissions inputs omitted all permissions installation token repository dispatch Contents write

💡 Result:

When using the actions/create-github-app-token GitHub Action, you can explicitly define permissions for the generated installation access token by using the permission-{permission-name} input format [1][2]. By default, if no permission-* inputs are provided, the generated token inherits all permissions granted to the GitHub App installation [3][2]. However, it is a recommended security practice to explicitly list only the permissions required for your specific workflow use case [1][2]. Regarding your specific scenario: 1. Repository Dispatch: Creating a repository_dispatch event requires the contents: write permission [4][5]. If you are manually configuring permissions and find your workflow failing with a "Resource not accessible by integration" error, ensure that permission-contents: write is explicitly included in your action configuration [4][5]. 2. Omitted Permissions: If you previously relied on the default behavior (inheriting all permissions) and are now experiencing "Resource not accessible by integration" errors after adding permission-* inputs, it is likely that you have omitted a required permission [6]. Because the action now strictly enforces the permissions you define, the token will lack any permissions not explicitly specified in your input list [7][6]. To troubleshoot or resolve missing permission issues: - Verify that the GitHub App installation actually possesses the required permissions (permissions must be granted to the installation, not just the app) [2]. - If you are unsure which permissions are required for a specific API endpoint, consult the GitHub REST API documentation for that endpoint [5]. - If you wish to revert to default behavior, you can remove all permission-* inputs, causing the token to inherit all installation permissions again [6]. Alternatively, you can explicitly add all necessary permissions one by one until the error resolves [6].

Citations:


🌐 Web query:

site:docs.github.com REST repositories create a repository dispatch event GitHub App installation access token Contents write

💡 Result:

To trigger a repository dispatch event using a GitHub App, you must make a POST request to the GitHub REST API endpoint: POST /repos/{owner}/{repo}/dispatches [1][2]. To perform this action using a GitHub App installation access token, the app must have at least read-level access to the "Contents" repository permission [3]. This permission is required to subscribe to and trigger the repository_dispatch event [3][4]. When making the request, you must provide: 1. event_type: A custom string (100 characters or fewer) used as the event name [1][2]. 2. client_payload: An optional JSON object containing data that your GitHub Actions workflow or GitHub App can use [1][2]. Authentication is performed by including the GitHub App installation access token in the Authorization header of your request: Authorization: Bearer YOUR_INSTALLATION_ACCESS_TOKEN [5].

Citations:


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- pinned action README permission behavior ---'
curl -fsSL \
  'https://raw.githubusercontent.com/actions/create-github-app-token/29824e69f54612133e76f7eaac726eef6c875baf/README.md' |
  rg -n -C 8 'permission-|all permissions|permissions' | head -160

printf '%s\n' '--- official repository dispatch documentation ---'
curl -fsSL \
  'https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28' |
  python3 - <<'PY'
import sys
from html.parser import HTMLParser

class Text(HTMLParser):
    def __init__(self):
        super().__init__()
        self.parts = []
    def handle_data(self, data):
        value = " ".join(data.split())
        if value:
            self.parts.append(value)

p = Text()
p.feed(sys.stdin.read())
text = " ".join(p.parts)
needle = "Create a repository dispatch event"
pos = text.find(needle)
if pos < 0:
    print("dispatch section not found")
else:
    print(text[pos:pos + 5000])
PY

Repository: getoptimum/optimum-gateway

Length of output: 4191


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

doc=/tmp/github-repos-rest.html
curl -fsSL \
  'https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28' \
  -o "$doc"

printf '%s\n' '--- repository dispatch section in official REST docs ---'
rg -in -C 12 \
  'create a repository dispatch event|repository_dispatch|Contents.*(read|write)|fine-grained' \
  "$doc" | head -240

printf '%s\n' '--- official REST API description ---'
api=/tmp/github-rest-api.yaml
curl -fsSL \
  'https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.yaml' \
  -o "$api"

rg -n -C 16 \
  '/repos/\{owner\}/\{repo\}/dispatches|repository dispatch|contents.*write|contents.*read' \
  "$api" | head -240

Repository: getoptimum/optimum-gateway

Length of output: 50383


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from html.parser import HTMLParser
from html import unescape

path = "/tmp/github-repos-rest.html"

class Parser(HTMLParser):
    def __init__(self):
        super().__init__()
        self.out = []
    def handle_data(self, data):
        text = " ".join(data.split())
        if text:
            self.out.append(text)

parser = Parser()
with open(path, encoding="utf-8") as f:
    parser.feed(f.read())

text = " ".join(parser.out)
start = text.find("Create a repository dispatch event")
if start < 0:
    raise SystemExit("dispatch section not found")

# Stop before the next endpoint heading.
end = text.find("Get the hash algorithm for a repository", start)
if end < 0:
    end = start + 7000

print(text[start:end][:9000])
PY

Repository: getoptimum/optimum-gateway

Length of output: 200


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from html import unescape
from html.parser import HTMLParser

path = "/tmp/github-repos-rest.html"
raw = open(path, encoding="utf-8").read()
needle = 'id="create-a-repository-dispatch-event"'
positions = []
pos = 0
while True:
    pos = raw.find(needle, pos)
    if pos < 0:
        break
    positions.append(pos)
    pos += len(needle)

print("heading occurrences:", len(positions), positions)
for i, pos in enumerate(positions):
    print(f"\n--- occurrence {i + 1} at {pos} ---")
    fragment = raw[max(0, pos - 500):pos + 12000]
    parser = HTMLParser()
    parts = []
    parser.handle_data = lambda data: parts.append(" ".join(data.split()))
    parser.feed(fragment)
    text = " ".join(x for x in parts if x)
    print(text[:9000])
PY

Repository: getoptimum/optimum-gateway

Length of output: 3600


Restrict both infrastructure App tokens.

Set permission-contents: write on infra-dispatch-token and infra-dispatch-token-dev. Both tokens call repos/getoptimum/infrastructure/dispatches. Without explicit permission-* inputs, the action inherits all permissions granted to the installation.

🧰 Tools
🪛 zizmor (1.29.0)

[error] 178-178: dangerous use of GitHub App tokens (github-app): app token inherits blanket installation permissions

(github-app)

🤖 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/docker-publish.yml around lines 176 - 183, Update both
token-generation steps identified by infra-dispatch-token and
infra-dispatch-token-dev to explicitly set permission-contents to write in their
action inputs. Keep the existing repository and authentication configuration
unchanged.

Sources: Path instructions, Linters/SAST tools


- name: Trigger Ansible deploy (main)
- name: Trigger Ansible deploy (development)
run: |
gh api repos/getoptimum/infrastructure/dispatches \
-f event_type=deploy_gateways \
-f "client_payload[tag]=dev-latest" \
-f "client_payload[digest]=${{ needs.docker.outputs.digest }}"
env:
GH_TOKEN: ${{ steps.infra-dispatch-token-main.outputs.token }}
GH_TOKEN: ${{ steps.infra-dispatch-token-dev.outputs.token }}
Loading