Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
142 changes: 142 additions & 0 deletions .github/workflows/adapters-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: Adapters CI

on:
push:
branches: [main, feat/*]
paths:
- 'adapters/**'
- '.github/workflows/adapters-ci.yml'
Comment on lines +3 to +8

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

if [ -f .github/workflows/adapters-ci.yml ]; then
  echo "== adapters-ci.yml lines 1-140 =="
  nl -ba .github/workflows/adapters-ci.yml | sed -n '1,140p'
else
  echo "missing .github/workflows/adapters-ci.yml"
fi

echo
echo "== github actions trigger/tag publish context =="
python3 - <<'PY'
from pathlib import Path
p = Path(".github/workflows/adapters-ci.yml")
if not p.exists():
    raise SystemExit
text = p.read_text()
start = text.index("on:")
end = text.find("\n", start)
print("on branch:", text[start:end])
print("tags present in workflows:", any("tags:" in line for line in text.splitlines()))
print("publish-adapters occurrences:", [i+1 for i,l in enumerate(text.splitlines()) if "publish-adapters" in l or "PYPI_VERSION" in l or "if: startsWith(github.ref" in l])
for i,l in enumerate(text.splitlines(), 1):
    if "publish-adapters" in l or "PYPI_VERSION" in l or "if: startsWith(github.ref" in l:
        print(f"{i}: {l}")
PY

Repository: Das-rebel/a3m-router

Length of output: 232


🏁 Script executed:

#!/bin/bash
set -euo pipefail

if [ -f .github/workflows/adapters-ci.yml ]; then
  echo "== adapters-ci.yml lines 1-140 =="
  nl -ba .github/workflows/adapters-ci.yml | sed -n '1,140p'
else
  echo "missing .github/workflows/adapters-ci.yml"
fi

echo
echo "== workflow trigger/tag publish context =="
python3 - <<'PY'
from pathlib import Path
p = Path(".github/workflows/adapters-ci.yml")
if not p.exists():
    raise SystemExit
text = p.read_text()
start = text.index("on:")
end = text.find("\n", start)
print("on branch:", text[start:end])
print("tags present in workflows:", any("tags:" in line for line in text.splitlines()))
print("publish-adapters occurrences:", [i+1 for i,l in enumerate(text.splitlines()) if "publish-adapters" in l or "PYPI_VERSION" in l or "if: startsWith(github.ref" in l])
for i,l in enumerate(text.splitlines(), 1):
    if "publish-adapters" in l or "PYPI_VERSION" in l or "if: startsWith(github.ref" in l:
        print(f"{i}: {l}")
PY

Repository: Das-rebel/a3m-router

Length of output: 232


🏁 Script executed:

#!/bin/bash
set -euo pipefail

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

python3 - <<'PY'
from pathlib import Path
p = Path(".github/workflows/adapters-ci.yml")
if not p.exists():
    raise SystemExit
text = p.read_text()
print("\nOn block:")
in_on = False
for line in text.splitlines():
    if line == "on:":
        in_on = True
        print(line)
        continue
    if in_on:
        if line and not line.startswith(' ') and not line.startswith('\t') and line != 'on:':
            break
        print(line)

print("\nRelevant occurrences:")
for i, line in enumerate(text.splitlines(), 1):
    if any(s in line for s in ["publish-adapters", "PYPI_VERSION", "startsWith(github.ref", "github.ref == 'refs/tags/'"]):
        print(f"{i}: {line}")
PY

Repository: Das-rebel/a3m-router

Length of output: 5132


Enable tag-triggered workflow runs.

push.branches filters out tag pushes, so publish-adapters never reaches its tag condition and cannot publish a versioned release.

Proposed fix
   push:
     branches: [main, feat/*]
+    tags: ['v*']
📝 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
on:
push:
branches: [main, feat/*]
paths:
- 'adapters/**'
- '.github/workflows/adapters-ci.yml'
on:
push:
branches: [main, feat/*]
tags: ['v*']
paths:
- 'adapters/**'
- '.github/workflows/adapters-ci.yml'
🤖 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/adapters-ci.yml around lines 3 - 8, Update the workflow
trigger configuration under push so tag pushes are included alongside the
existing branch and path filters, allowing publish-adapters to evaluate its tag
condition and publish versioned releases.

pull_request:
branches: [main]
paths:
- 'adapters/**'
- '.github/workflows/adapters-ci.yml'

jobs:
test-adapters:
Comment on lines +15 to +16

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,170p' .github/workflows/adapters-ci.yml | nl -ba

echo
echo "== diff stat/name only relevant workflow =="
git diff -- .github/workflows/adapters-ci.yml | sed -n '1,220p'

echo
echo "== permissions references in workflow =="
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/adapters-ci.yml')
for i,line in enumerate(p.read_text().splitlines(), 1):
    if 'permissions:' in line or 'GITHUB_TOKEN' in line or 'packages:' in line:
        print(f'{i}: {line}')
PY

Repository: Das-rebel/a3m-router

Length of output: 221


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
sed -n '1,170p' .github/workflows/adapters-ci.yml | awk '{printf "%6d  %s\n", NR, $0}'

echo
echo "== diff stat and relevant hunk =="
git diff -- .github/workflows/adapters-ci.yml | head -n 220 || true

echo
echo "== permissions/token/package references in workflow =="
python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/adapters-ci.yml')
for i, line in enumerate(p.read_text().splitlines(), 1):
    if any(s in line for s in ('permissions:', 'GITHUB_TOKEN', 'packages:', 'ghcr.io', 'docker/login-action', 'docker/build-push-action')):
        print(f'{i}: {line}')
PY

echo
echo "== jobs named docker-build and uses of packages permissions =="
python3 - <<'PY'
from pathlib import Path
text = Path('.github/workflows/adapters-ci.yml').read_text()
lines = text.splitlines()
for i, line in enumerate(lines, 1):
    if line.rstrip() == 'docker-build:':
        print(f'job docker-build starts around line {i}')
        # Print context until next top-level key
        for j in range(i, min(len(lines), i + 80)):
            print(f'{j}: {lines[j-1]}')
            if lines[j-1].startswith('  ') and not lines[j-1].strip().startswith('-') and lines[j-1][0] in ' \t':
                # simple continuation until file end or next top-level (not indent under job?) keep until next top-level key
                pass
        break
PY

Repository: Das-rebel/a3m-router

Length of output: 5594


Set least-privilege token permissions.

The workflow uses repository-default GITHUB_TOKEN permissions. Set contents: read globally and add packages: write only on docker-build, which logs in to GHCR and pushes the 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/adapters-ci.yml around lines 15 - 16, Set workflow-wide
GITHUB_TOKEN permissions to contents: read, then add packages: write
specifically to the docker-build job that authenticates with GHCR and pushes the
image. Keep all other jobs limited to the global read-only contents permission.

Source: Linters/SAST tools

runs-on: ubuntu-latest

strategy:
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Cache pip packages
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('adapters/**/requirements*.txt') }}

- name: Install dependencies
run: |
cd adapters
pip install -e .
pip install pytest pytest-asyncio black isort flake8

- name: Lint with black
run: |
cd adapters
black --check a3m_adapter/ --exclude='/(\.git|\.venv|__pycache__)/'

- name: Lint with isort
run: |
cd adapters
isort --check-only a3m_adapter/ --exclude='/(\.git|\.venv|__pycache__)/'

- name: Lint with flake8
run: |
cd adapters
flake8 a3m_adapter/ --max-line-length=100 --exclude='/(\.git|\.venv|__pycache__)/'

- name: Run tests
run: |
cd adapters
pytest a3m_adapter/tests/ -v --tb=short

test-integration:
runs-on: ubuntu-latest
needs: test-adapters

steps:
- uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Start A3M Router
run: |
npx a3m-router serve &
sleep 5
curl -f http://localhost:8787/health || exit 1

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install adapter and test deps
run: |
cd adapters
pip install -e .
pip install requests pytest pytest-asyncio

- name: Run integration tests
run: |
cd adapters
pytest a3m_adapter/tests/ -v --tb=short -k "integration"

publish-adapters:
runs-on: ubuntu-latest
needs: test-integration
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Publish to PyPI
env:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: |
cd adapters
pip install build twine
python -m build
twine upload --token $PYPI_TOKEN dist/*

docker-build:
runs-on: ubuntu-latest
needs: test-integration
if: github.event_name == 'push'

steps:
- uses: actions/checkout@v4

- name: Build Docker image
run: |
docker build -t ghcr.io/das-rebel/a3m-router:latest .

- name: Run container health check
run: |
docker run -d --name a3m-test -p 8787:8787 ghcr.io/das-rebel/a3m-router:latest
sleep 5
curl -f http://localhost:8787/health
docker stop a3m-test

- name: Push to GHCR
if: github.event_name == 'push'
run: |
echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker push ghcr.io/das-rebel/a3m-router:latest
Comment on lines +119 to +142

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 "== files =="
git ls-files .github/workflows/adapters-ci.yml || true

if [ -f .github/workflows/adapters-ci.yml ]; then
  echo "== workflow excerpt =="
  sed -n '1,220p' .github/workflows/adapters-ci.yml | cat -n
fi

echo "== search branch filters and docker-push usage =="
rg -n "branches:|branches-ignore|paths-ignore|docker-build|docker push|ghcr.io/das-rebel/a3m-router|GITHUB_TOKEN|github.event_name|github.ref_name|github.ref" .github/workflows || true

Repository: Das-rebel/a3m-router

Length of output: 6244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

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

p = Path(".github/workflows/adapters-ci.yml")
text = p.read_text() if p.exists() else ""

print("== docker-build step names ==")
m = re.search(r'^  docker-build:(\n[ \t]+.*?)(?:\n  [a-zA-Z0-9_-]+:|\Z)', text, re.S)
print(m.group(1) if m else "not found")

print("== condition on triggers containing 'push' ==")
for rexp in [
    r'^on:\s*$\n((?:(?:[ \t]+[^\n]+)|(?:[ \t]+push:[^\n]*)|(?:(?:\n[ \t][ \t-]?(?:branches|branches-ignore|paths|paths-ignore):[^\n]*)|(?:[ \t]+(?:(?:[^\n])))*)+)*)',
    r'^[ \t]*push:(.*?)(?=\n\n|\n  [a-zA-Z0-9_-]+:|\Z)',
    r'^[ \t]{2}branches:(.*?)(?=\n  branches-ignore:|\n[ \t]{2}[a-zA-Z0-9_-]+:|\Z)',
]:
    alls = re.findall(rexp, text, re.S | re.M)
    if alls:
        print(rexp[:60], "-", alls[:5])

print("== deterministic parse-ish indicators ==")
checks = {
    "workflow_file_exists": p.exists(),
    "push_trigger": bool(re.search(r'^ *push:', text, flags=re.M)),
    "docker_push_exists": "docker push ghcr.io/das-rebel/a3m-router:latest" in text,
    "login_exists": "docker login ghcr.io" in text,
    "checks_main_or_all_branches": any(x in text for x in ["ghcr.io/das-rebel/a3m-router:latest", "latest", "branches:"] and not re.search(r"branches:\n[ \t]+- *refs/heads/[^\\n]*main|branches:\n[ \t]+- *[\*$]|\n\s*branches-ignore:", text)),
}
for k,v in checks.items():
    print(f"{k}={v}")
PY

Repository: Das-rebel/a3m-router

Length of output: 641


Do not publish latest from feature branches.

docker-build runs on push events from main and feat/*, then pushes ghcr.io/das-rebel/a3m-router:latest. This can overwrite latest with unreviewed feature-branch code.

Restrict the registry login and push steps to main, or publish immutable branch-specific tags instead.

🧰 Tools
🪛 zizmor (1.29.0)

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

(artipacked)


[warning] 119-143: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)


[error] 141-141: 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/adapters-ci.yml around lines 119 - 142, Restrict the
docker-build registry login and image push to pushes on the main branch, not
merely any push event; update the “Push to GHCR” condition and apply the same
guard to the preceding GHCR login command so feature-branch builds never publish
the latest tag.

Loading
Loading