Skip to content
Open
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
186 changes: 186 additions & 0 deletions .github/workflows/okp-quality-gate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
name: OKP Quality Gate

on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'src/llama_stack_configuration.py'
- 'src/constants.py'
- 'src/configuration.py'
- 'src/models/config.py'
- 'src/utils/vector_search.py'
- 'src/utils/reranker.py'
- 'src/app/endpoints/query.py'
- 'src/app/endpoints/streaming_query.py'
- 'src/app/endpoints/responses.py'
- 'src/app/endpoints/rags.py'
- 'src/client.py'
- 'providers/**/solr_vector_io/**'
- 'deploy/lightspeed-stack/Containerfile'
- 'docker-compose-library.yaml'

permissions:
pull-requests: write

jobs:
okp-quality-gate:
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
Comment on lines +25 to +29

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add explicit least-privilege permissions and a concurrency group.

The job has no permissions: block (defaults to broad GITHUB_TOKEN scopes) and no concurrency group, so repeated pushes to the same PR (synchronize) can trigger multiple overlapping GitLab pipelines instead of canceling the stale one.

🛡️ Proposed fix
+permissions:
+  contents: read
+
+concurrency:
+  group: okp-quality-gate-${{ github.event.pull_request.number }}
+  cancel-in-progress: true
+
 jobs:
   okp-quality-gate:
     runs-on: ubuntu-latest
     timeout-minutes: 120
📝 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
jobs:
okp-quality-gate:
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
permissions:
contents: read
concurrency:
group: okp-quality-gate-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
okp-quality-gate:
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
🧰 Tools
🪛 zizmor (1.26.1)

[info] 23-23: workflow or action definition without a name (anonymous-definition): this job

(anonymous-definition)

🤖 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/okp-quality-gate.yaml around lines 22 - 26, The
okp-quality-gate job is missing an explicit least-privilege permissions block
and a concurrency setting. Update the workflow job in okp-quality-gate to add a
restrictive permissions stanza for the GITHUB_TOKEN, and define a concurrency
group keyed to the PR or branch so stale runs are canceled when a new
synchronize push arrives. Keep the change scoped to the okp-quality-gate job and
its job-level settings.

Source: Linters/SAST tools

- name: Trigger GitLab evaluation pipeline
id: trigger
env:
GITLAB_TRIGGER_TOKEN: ${{ secrets.GITLAB_TRIGGER_TOKEN }}
GITLAB_PROJECT_ID: ${{ vars.GITLAB_PROJECT_ID }}
run: |
RESPONSE=$(curl --silent --show-error --fail \
--request POST \
--form "token=${GITLAB_TRIGGER_TOKEN}" \
--form "ref=ci-trigger-test" \
--form "variables[PR_REPO]=${{ github.event.pull_request.head.repo.full_name }}" \
--form "variables[PR_BRANCH]=${{ github.event.pull_request.head.ref }}" \
--form "variables[PR_SHA]=${{ github.event.pull_request.head.sha }}" \
"https://gitlab.cee.redhat.com/api/v4/projects/${GITLAB_PROJECT_ID}/trigger/pipeline")
Comment on lines +36 to +43

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Script injection via untrusted PR branch ref in shell run: block.

github.event.pull_request.head.ref and head.repo.full_name are attacker-controlled and are interpolated directly into a run: shell script (--form arguments). A crafted branch name (e.g. containing backticks or $(...)) can break out of the --form argument and execute arbitrary code on the runner, exposing GITLAB_TRIGGER_TOKEN/GITLAB_API_TOKEN. This matches the exact pattern flagged by both zizmor (template-injection, lines 37-39) and actionlint (untrusted expression, line 32-area). GitHub's official guidance is to pass such values through intermediate environment variables first.

The steps.trigger.outputs.pipeline_id interpolation at line 59 is lower risk (self-produced numeric output) but should follow the same pattern for consistency and defense-in-depth.

🔒 Proposed fix using intermediate env vars
       - name: Trigger GitLab evaluation pipeline
         id: trigger
         env:
           GITLAB_TRIGGER_TOKEN: ${{ secrets.GITLAB_TRIGGER_TOKEN }}
           GITLAB_PROJECT_ID: ${{ vars.GITLAB_PROJECT_ID }}
+          PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
+          PR_BRANCH: ${{ github.event.pull_request.head.ref }}
+          PR_SHA: ${{ github.event.pull_request.head.sha }}
         run: |
           RESPONSE=$(curl --silent --show-error --fail \
             --request POST \
             --form "token=${GITLAB_TRIGGER_TOKEN}" \
             --form "ref=ci-trigger-test" \
-            --form "variables[PR_REPO]=${{ github.event.pull_request.head.repo.full_name }}" \
-            --form "variables[PR_BRANCH]=${{ github.event.pull_request.head.ref }}" \
-            --form "variables[PR_SHA]=${{ github.event.pull_request.head.sha }}" \
+            --form "variables[PR_REPO]=${PR_REPO}" \
+            --form "variables[PR_BRANCH]=${PR_BRANCH}" \
+            --form "variables[PR_SHA]=${PR_SHA}" \
             "https://gitlab.cee.redhat.com/api/v4/projects/${GITLAB_PROJECT_ID}/trigger/pipeline")
       - name: Wait for GitLab pipeline to complete
         env:
           GITLAB_API_TOKEN: ${{ secrets.GITLAB_API_TOKEN }}
           GITLAB_PROJECT_ID: ${{ vars.GITLAB_PROJECT_ID }}
+          PIPELINE_ID: ${{ steps.trigger.outputs.pipeline_id }}
         run: |
-          PIPELINE_ID=${{ steps.trigger.outputs.pipeline_id }}
           echo "Waiting for pipeline #${PIPELINE_ID}..."

Also applies to: 59-59

🧰 Tools
🪛 zizmor (1.26.1)

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

(template-injection)


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

(template-injection)


[warning] 39-39: 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/okp-quality-gate.yaml around lines 33 - 40, The `run`
block in the pipeline trigger step is interpolating attacker-controlled pull
request values from `github.event.pull_request.head.ref` and
`head.repo.full_name` directly into curl arguments, so move these expressions
into intermediate environment variables and reference only the env vars inside
the shell script. Update the `trigger` step in the workflow so the `curl` call
uses safe shell variables, and apply the same pattern to the
`steps.trigger.outputs.pipeline_id` usage for consistency and defense-in-depth.

Source: Linters/SAST tools


PIPELINE_ID=$(echo "$RESPONSE" | jq -r '.id')
PIPELINE_URL=$(echo "$RESPONSE" | jq -r '.web_url')

if [ "$PIPELINE_ID" = "null" ] || [ -z "$PIPELINE_ID" ]; then
echo "Failed to trigger pipeline. Response:"
echo "$RESPONSE"
exit 1
fi

echo "pipeline_id=${PIPELINE_ID}" >> "$GITHUB_OUTPUT"
echo "pipeline_url=${PIPELINE_URL}" >> "$GITHUB_OUTPUT"
echo "Triggered GitLab pipeline #${PIPELINE_ID}: ${PIPELINE_URL}"

- name: Wait for GitLab pipeline to complete
id: wait
env:
GITLAB_API_TOKEN: ${{ secrets.GITLAB_API_TOKEN }}
GITLAB_PROJECT_ID: ${{ vars.GITLAB_PROJECT_ID }}
run: |
PIPELINE_ID=${{ steps.trigger.outputs.pipeline_id }}
echo "Waiting for pipeline #${PIPELINE_ID}..."

while true; do
RESPONSE=$(curl --silent --show-error \
--header "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" \
"https://gitlab.cee.redhat.com/api/v4/projects/${GITLAB_PROJECT_ID}/pipelines/${PIPELINE_ID}")
Comment on lines +68 to +70

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Add a request timeout to curl calls in the polling loop.

Neither curl invocation sets --max-time/--connect-timeout. A network stall could leave a step hanging without clear diagnostics until the overall timeout-minutes: 120 kills the job.

🤖 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/okp-quality-gate.yaml around lines 63 - 65, The polling
loop’s curl request in the workflow can hang indefinitely because it only uses
--silent and --show-error. Update the curl call in the pipeline polling logic to
include a request timeout such as --max-time and/or --connect-timeout, using the
existing RESPONSE assignment block that fetches the GitLab pipeline status, so
stalled network calls fail fast with clearer diagnostics.


STATUS=$(echo "$RESPONSE" | jq -r '.status')

case "$STATUS" in
success)
echo "Pipeline #${PIPELINE_ID} passed!"
echo "status=success" >> "$GITHUB_OUTPUT"
break
;;
failed)
echo "Pipeline #${PIPELINE_ID} failed."
echo "status=failed" >> "$GITHUB_OUTPUT"
break
;;
canceled)
echo "Pipeline #${PIPELINE_ID} was canceled."
echo "status=canceled" >> "$GITHUB_OUTPUT"
break
;;
created|waiting_for_resource|preparing|pending|running)
echo "Status: ${STATUS}. Checking again in 60s..."
sleep 60
;;
*)
echo "Unexpected status: ${STATUS}. Checking again in 60s..."
sleep 60
;;
esac
done

- name: Fetch evaluation results from GitLab
id: results
if: always() && steps.wait.outputs.status != 'canceled'
env:
GITLAB_API_TOKEN: ${{ secrets.GITLAB_API_TOKEN }}
GITLAB_PROJECT_ID: ${{ vars.GITLAB_PROJECT_ID }}
run: |
PIPELINE_ID=${{ steps.trigger.outputs.pipeline_id }}

JOB_ID=$(curl --silent --show-error \
--header "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" \
"https://gitlab.cee.redhat.com/api/v4/projects/${GITLAB_PROJECT_ID}/pipelines/${PIPELINE_ID}/jobs" \
| jq -r '[.[] | select(.name == "stack_test")][0].id')

if [ "$JOB_ID" = "null" ] || [ -z "$JOB_ID" ]; then
echo "Could not find stack_test job in pipeline"
echo "has_report=false" >> "$GITHUB_OUTPUT"
exit 0
fi

HTTP_CODE=$(curl --silent --output regression_report.md --write-out "%{http_code}" \
--header "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" \
"https://gitlab.cee.redhat.com/api/v4/projects/${GITLAB_PROJECT_ID}/jobs/${JOB_ID}/artifacts/lightspeed-evaluation/eval_output/ci_run/regression_report.md")

if [ "$HTTP_CODE" = "200" ] && [ -s regression_report.md ]; then
echo "has_report=true" >> "$GITHUB_OUTPUT"
else
echo "No regression report artifact found (HTTP ${HTTP_CODE})"
echo "has_report=false" >> "$GITHUB_OUTPUT"
fi

- name: Post results to PR
if: always() && steps.wait.outputs.status != ''
uses: actions/github-script@v7

Copy link
Copy Markdown
Contributor

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
# Fetch the latest commit SHA for actions/github-script@v7
curl -s https://api.github.com/repos/actions/github-script/git/refs/tags/v7 | jq -r '.object.sha'

Repository: lightspeed-core/lightspeed-stack

Length of output: 175


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the workflow context around the reported line
nl -ba .github/workflows/okp-quality-gate.yaml | sed -n '110,155p'

# Resolve the v7 tag correctly (dereference annotated tags if needed)
python3 - <<'PY'
import json, urllib.request

repo = "actions/github-script"
ref_url = f"https://api.github.com/repos/{repo}/git/refs/tags/v7"
req = urllib.request.Request(ref_url, headers={"Accept": "application/vnd.github+json", "User-Agent": "code-rabbit"})
with urllib.request.urlopen(req) as r:
    ref = json.load(r)

print("ref_object_type:", ref.get("object", {}).get("type"))
print("ref_object_sha:", ref.get("object", {}).get("sha"))

if ref.get("object", {}).get("type") == "tag":
    tag_sha = ref["object"]["sha"]
    tag_url = f"https://api.github.com/repos/{repo}/git/tags/{tag_sha}"
    req = urllib.request.Request(tag_url, headers={"Accept": "application/vnd.github+json", "User-Agent": "code-rabbit"})
    with urllib.request.urlopen(req) as r:
        tag = json.load(r)
    print("tag_object_type:", tag.get("object", {}).get("type"))
    print("tag_commit_sha:", tag.get("object", {}).get("sha"))
PY

Repository: lightspeed-core/lightspeed-stack

Length of output: 210


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the workflow context around the reported line without nl
awk 'NR>=110 && NR<=155 { print NR ":" $0 }' .github/workflows/okp-quality-gate.yaml

# Resolve actions/github-script@v7 to a commit if the API is reachable
python3 - <<'PY'
import json, urllib.request, sys

repo = "actions/github-script"
headers = {"Accept": "application/vnd.github+json", "User-Agent": "code-rabbit"}

def get(url):
    req = urllib.request.Request(url, headers=headers)
    with urllib.request.urlopen(req, timeout=20) as r:
        return json.load(r)

try:
    ref = get(f"https://api.github.com/repos/{repo}/git/refs/tags/v7")
    print("ref_json:", json.dumps(ref, sort_keys=True))
    obj = ref.get("object", {})
    if obj.get("type") == "tag":
        tag = get(f"https://api.github.com/repos/{repo}/git/tags/{obj['sha']}")
        print("tag_json:", json.dumps(tag, sort_keys=True))
    elif obj.get("type") == "commit":
        print("commit_sha:", obj.get("sha"))
except Exception as e:
    print("ERROR:", type(e).__name__, e)
    sys.exit(0)
PY

Repository: lightspeed-core/lightspeed-stack

Length of output: 2821


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the workflow header and permissions/event context
awk 'NR>=1 && NR<=60 { print NR ":" $0 }' .github/workflows/okp-quality-gate.yaml

Repository: lightspeed-core/lightspeed-stack

Length of output: 2447


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json, ssl, urllib.request, sys

repo = "actions/github-script"
headers = {"Accept": "application/vnd.github+json", "User-Agent": "code-rabbit"}
ctx = ssl._create_unverified_context()

def get(url):
    req = urllib.request.Request(url, headers=headers)
    with urllib.request.urlopen(req, context=ctx, timeout=20) as r:
        return json.load(r)

ref = get(f"https://api.github.com/repos/{repo}/git/refs/tags/v7")
print("ref_type:", ref.get("object", {}).get("type"))
print("ref_sha:", ref.get("object", {}).get("sha"))

obj = ref.get("object", {})
if obj.get("type") == "tag":
    tag = get(f"https://api.github.com/repos/{repo}/git/tags/{obj['sha']}")
    print("tag_type:", tag.get("object", {}).get("type"))
    print("commit_sha:", tag.get("object", {}).get("sha"))
else:
    print("commit_sha:", obj.get("sha"))

sha = (tag.get("object", {}).get("sha") if obj.get("type") == "tag" else obj.get("sha"))
if sha:
    commit = get(f"https://api.github.com/repos/{repo}/commits/{sha}")
    print("commit_exists:", commit.get("sha") == sha)
PY

Repository: lightspeed-core/lightspeed-stack

Length of output: 1166


Pin actions/github-script to a commit SHA .github/workflows/okp-quality-gate.yaml:134@v7 is mutable, and this step runs with pull-requests: write, so a moved tag could let arbitrary code post or modify PR content.

🧰 Tools
🪛 zizmor (1.26.1)

[error] 134-134: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 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/okp-quality-gate.yaml at line 134, Replace the mutable
actions/github-script@v7 reference in the workflow step with a specific
immutable commit SHA, retaining the existing action version and adding a comment
identifying the pinned release.

Source: Linters/SAST tools

env:
PIPELINE_STATUS: ${{ steps.wait.outputs.status }}
PIPELINE_URL: ${{ steps.trigger.outputs.pipeline_url }}
HAS_REPORT: ${{ steps.results.outputs.has_report }}
with:
script: |
const fs = require('fs');
const status = process.env.PIPELINE_STATUS;
const pipelineUrl = process.env.PIPELINE_URL;
const hasReport = process.env.HAS_REPORT === 'true';

const statusEmoji = status === 'success' ? '✅' : status === 'failed' ? '❌' : '⚠️';
let body = `## ${statusEmoji} OKP Quality Gate: ${status}\n\n`;
body += `[GitLab Pipeline](${pipelineUrl})\n\n`;

if (hasReport) {
const report = fs.readFileSync('regression_report.md', 'utf8');
body += `<details>\n<summary>Regression Report</summary>\n\n${report}\n\n</details>\n`;
} else if (status === 'failed') {
body += `No regression report was generated. Check the [pipeline logs](${pipelineUrl}) for details.\n`;
}

const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('OKP Quality Gate')
);

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}

- name: Fail if pipeline did not succeed
if: steps.wait.outputs.status != 'success'
run: |
echo "Pipeline status: ${{ steps.wait.outputs.status }}"
exit 1
Loading