Skip to content

Feat: AIAC Phase 1 runnable UC-1 onboarding demo walkthrough - #725

Open
oblinder wants to merge 14 commits into
rossoctl:mainfrom
s-and-p-team:aiac-phase1-demo
Open

Feat: AIAC Phase 1 runnable UC-1 onboarding demo walkthrough#725
oblinder wants to merge 14 commits into
rossoctl:mainfrom
s-and-p-team:aiac-phase1-demo

Conversation

@oblinder

@oblinder oblinder commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What & why

Adds the runnable UC-1 onboarding demo / walkthrough that PR #710 explicitly deferred to a
follow-up PR. This completes the demo portion of #645 (Phase 1: functional AIAC agent + onboarding
demo).

PR #710 shipped the implementation, the integration-test ladder, and the demo workloads; this PR
adds the live, Makefile-driven walkthrough that ties them together.

The demo flow

demo/use-cases/uc1-onboarding/ is a live-only demo (real cluster, real Keycloak, real LLM call,
real RFC 8693 token exchange). It reproduces the same pipeline exercised by the rung-1/2/3
integration tests (test/integration/test_uc1_onboard_*.py, spec
docs/specs/integration-test/uc1-onboarding-pipeline.md):

make prereqs / clear / setup      # cluster + AIAC stack + workloads, users/roles, policy.md, token exchange
make onboard-agent                # AIAC discovers github-agent, reads policy.md -> inbound gate
make onboard-tool                 # discovers github-tool's scopes -> completes the agent's outbound gate
                                  #   (PCE additive merge; onboarding order is irrelevant)
make dev / test / devops          # real login + RFC 8693 exchange, evaluate generated Rego via `opa eval`

Nobody hand-writes the access rules: a two-line plain-English policy plus discovered capabilities
produce the inbound (who may call the agent) and outbound (what it may do downstream) Rego gates.

Contents

  • demo/use-cases/uc1-onboarding/Makefile, init/, onboard/, run/, lib/,
    show-state.py, demo.md
  • demo/assets/github-agent + github-tool workloads, k8s manifests, and INSTALL.md
  • docs/specs/demo/{github-agent,github-tool}.md
  • scripts/opa-kind-{enable,restore}.sh — toggle the OPA filesystem-stub writer on a Kind cluster
  • agent-skills / issue-tracking config under aiac/docs/agents/ + aiac/CLAUDE.md
  • Fix two off-by-one relative links in demo.md

Testing

The demo reuses the same scenario oracle and harness as the green -m integration rung-1/2/3
ladder. A full end-to-end run requires a live Kagenti cluster + Keycloak + an LLM endpoint
(make prereqs / make demo).

Addresses #645. Follows #710.

Assisted-By: Claude (Anthropic AI) noreply@anthropic.com

Summary by CodeRabbit

  • New Features

    • Added a containerized GitHub tool and agent demo with Kubernetes deployment support.
    • Added an idempotent installer with component selection, image handling, rollout checks, and verification.
    • Added a UC-1 onboarding workflow with authorization scenarios, policy generation, snapshots, and state comparison.
    • Added configurable tool selection and improved asynchronous agent execution.
  • Bug Fixes

    • Improved handling of incomplete agent results.
    • Resolved demo port configuration and deployment path issues.
  • Documentation

    • Added installation, onboarding, issue-tracking, triage, and domain guidance.

oblinder and others added 8 commits August 2, 2026 23:15
Split demo/ into demo/assets/ (reusable github_agent + github_tool
workloads) and demo/use-cases/ (populated by later handoffs), per
handoff 01. Consolidates the previously split/conflicting deployment
docs into a single demo/assets/INSTALL.md and adds an idempotent
demo/assets/install.sh.

- git mv demo/{agents,tools} -> demo/assets/{agents,tools}
- Fix github_tool/test/conftest.py parents[4] -> parents[5] for the
  new nesting depth (scenario import from test/integration)
- Recreate github_agent's uv venv and verify both workloads' own test
  suites pass from their new locations
- Update path references in docs/specs/demo/*.md (docs/issues and
  docs/gh-issues are gitignored; updated on disk but not tracked here)
- Point github_agent/README.md's "Deploying to Kagenti" section at
  the new consolidated INSTALL.md instead of duplicating steps

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
The AuthBridge sidecar reuses a workload's declared PORT as its own
reverse-proxy listener and shifts the app's real listen port to
PORT+1. AuthBridge also has a fixed health-check listener hardcoded
to 9091. github-tool declared PORT=9090, shifting the app to 9091 and
colliding with that fixed listener — the container crash-looped
fighting the sidecar for the port.

Found by actually running install.sh against the live cluster (not
caught by static review): the tool rolled out but crash-looped at
1/2 ready.

- Change github-tool's PORT/containerPort/probes/targetPort from 9090
  to 9095 (shifted: 9096), clear of every AuthBridge-fixed port (8080,
  8081, 9091, 9093, 9094). The Service's external port stays 9090.
- Document the port-shift invariant in docs/specs/demo/github-tool.md
  §7 and demo/assets/INSTALL.md.

Verified live: rollout succeeded, pod 2/2 Running with 0 restarts,
and a port-forwarded MCP POST to /mcp reaches AuthBridge's
JWT-validation layer as expected.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Adds demo/use-cases/uc1-onboarding/: a strictly-live, external-facing
demo proving AIAC discovers an agent and a tool, reads a two-line
plain-English policy, and generates enforceable least-privilege
authorization via RFC 8693 token exchange. Makefile + numbered Python
scripts organized into init/ (prereqs, clear, setup), onboard/ (agent,
tool), run/ (per-user drivers), and lib/ (shared scenario/helpers).

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Signed-off-by: Anatoly Koyfman <anatoly@il.ibm.com>
…config

- CLAUDE.md: issue-tracking section describes the GitHub issues/AIAC Project layout (no migration history); adds an '## Agent skills' block wiring the Matt Pocock engineering skills.
- .gitignore: drop obsolete docs/issues/ and docs/gh-issues/ entries.
- docs/agents/: issue-tracker, triage-labels, and domain config the skills read from.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Relocates demo/use-cases/uc1-onboarding/init/setup_keycloak.py to
lib/, alongside the other shared demo helpers, and updates the
lib/scenario.py comment that referenced its old path.

Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@oblinder, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 093ee1be-e96d-4b0d-9f7f-9e2df2bb738e

📥 Commits

Reviewing files that changed from the base of the PR and between 88603a1 and 6e9b4db.

📒 Files selected for processing (2)
  • aiac/demo/use-cases/uc1-onboarding/lib/setup_keycloak.py
  • aiac/demo/use-cases/uc1-onboarding/onboard/04-onboard-tool.py
📝 Walkthrough

Walkthrough

The change adds GitHub agent and MCP tool demo assets, a complete UC-1 authorization workflow with Keycloak integration, Kubernetes deployment infrastructure, installation helpers, OPA Kind scripts, and updated documentation.

Changes

Documentation and repository guidance

Layer / File(s) Summary
Documentation updates
aiac/.gitignore, aiac/CLAUDE.md, aiac/docs/agents/*, aiac/docs/specs/demo/*
Repository configuration removes local issue docs paths. Documentation adds issue tracking conventions for GitHub and AIAC Project, triage label mappings, domain documentation guidance, and updates agent/tool asset paths.

GitHub agent configuration and runtime

Layer / File(s) Summary
Configuration and data models
aiac/demo/assets/agents/github_agent/github_agent/config.py, data_types.py, event.py
Settings load environment variables and .env, define GitHub query structures with JSON number coercion, and establish an abstract async event contract.
LLM and CrewAI orchestration
aiac/demo/assets/agents/github_agent/github_agent/llm.py, agents.py, tools.py, prompts.py
CrewLLM builds from settings with extra headers and Ollama context windows. GithubAgents initializes prerequisite extractors and GitHub researchers with MCP tools. Tools module groups read/write scopes, filters by configuration, and logs exclusions. Prompts require read-only tool access and JSON null for missing values.
Async lifecycle and execution
aiac/demo/assets/agents/github_agent/a2a_agent.py, github_agent/main.py
MCP adapter entry and exit run on a dedicated worker thread. Tool validation and agent execution remain asynchronous. Results handle missing task output gracefully.

GitHub agent deployment and testing

Layer / File(s) Summary
Deployment manifests
aiac/demo/assets/agents/github_agent/k8s/configmaps.yaml, github-agent-deployment.yaml
ConfigMaps configure Keycloak connectivity and token exchange for github-tool-mcp. Deployment, Service, and AgentRuntime enable AuthBridge/SPIRE injection, configure Ollama and MCP endpoints, restrict capabilities, define probes and resource limits, and mount shared storage.
Container and dependencies
aiac/demo/assets/agents/github_agent/Dockerfile, .dockerignore, .env.template, pyproject.toml
Python 3.12 image with uv installs dependencies, sets production environment, and runs as non-root. Updated cryptography>=50.0.0 and added aiohttp>=3.14.3.
Tests and verification
aiac/demo/assets/agents/github_agent/test/test_*.py, test_startup.exp
Card metadata, skills, security, JSON-RPC, endpoints, well-known routes, agent card endpoints, prerequisite parsing and JSON coercion, tool selection defaults and exclusions, and tool catalog filtering are tested. Startup script detects Uvicorn readiness.

GitHub tool service

Layer / File(s) Summary
Tool server and deployment
aiac/demo/assets/tools/github_tool/server.py, Dockerfile, k8s/github-tool-deployment.yaml, requirements.txt
FastMCP server exposes four stub tools over HTTP. Deployment uses internal port 9095 behind Service port 9090 to avoid AuthBridge conflicts. Container is digest-pinned Python 3.12 slim. Dependencies include mcp[cli], uvicorn[standard], httpx, pytest, and pytest-asyncio.
Tool tests and configuration
aiac/demo/assets/tools/github_tool/test/conftest.py, test_server.py, pytest.ini
Tests verify tool names, descriptions, input schemas, and tool invocation with valid responses.

Demo installation infrastructure

Layer / File(s) Summary
Installation script
aiac/demo/assets/install.sh, aiac/demo/assets/INSTALL.md
Installer validates prerequisites, detects container runtime, builds or reuses images, loads images into Kind, applies manifests, and waits for rollouts. Tool and agent installation run independently. Installation guide documents procedures and excluded infrastructure.

UC-1 authorization workflow

Layer / File(s) Summary
Scenario and policies
aiac/demo/use-cases/uc1-onboarding/lib/scenario.py
Scenario defines demo users, roles, scopes, authorization pairs, least-privilege policy, frozen intents, and oracle functions for inbound and outbound access without external dependencies.
Shared utilities
aiac/demo/use-cases/uc1-onboarding/lib/_lib.py, setup_keycloak.py
Utilities provide terminal narration, environment loading, strict kubectl/OPA execution, Keycloak realm and user provisioning, policy store operations, service onboarding, JWT decoding, ROPC login, RFC 8693 token exchange, and end-to-end authorization evaluation. Keycloak setup idempotently ensures ROPC client, token exchange, and default audience scope.
Initialization and state management
aiac/demo/use-cases/uc1-onboarding/init/00-prereqs.py, init/01-clear.py, init/02-setup.py
Prerequisite verification checks cluster, CRDs, namespace, SPIRE, Keycloak, conditionally deploys AIAC and workloads, polls for client registration, and validates MCP label. Cleanup removes provisioned Keycloak roles, clears Policy Store, clears writer Rego, and recreates snapshots directory. Setup provisions users and roles, mounts policy, resolves client IDs, configures token exchange.
Onboarding and scenarios
aiac/demo/use-cases/uc1-onboarding/onboard/03-onboard-agent.py, onboard/04-onboard-tool.py, run/run-*.py, show-state.py
Agent onboarding resolves service ID, onboards through Controller, captures generated Rego, validates policies. Tool onboarding resolves service ID, onboards through Controller, captures Rego, assigns tool audience scope. User runners import shared drive and execute flows for dev-user, devops-user, test-user. State tool displays Keycloak users, roles, scopes, generated Rego files, grant tables, and snapshot diffs with prior comparisons.
Demo orchestration
aiac/demo/use-cases/uc1-onboarding/Makefile, demo.md
Makefile invokes prerequisite, reset, setup, onboarding, and state inspection targets. Guide documents scenario, policy, generated authorization, role-to-scope mappings, real-cluster setup, staged verification, user scenarios, architecture, troubleshooting, and known limitations.

Kind OPA integration

Layer / File(s) Summary
OPA enablement and restore
scripts/opa-kind-enable.sh, scripts/opa-kind-restore.sh
Enable script builds and loads AuthBridge, deploys bundle-service, generates temporary Helm overlay with OPA and three parsers in inbound and outbound pipelines, configures JWT validation, and restarts agent pods. Restore script runs Helm upgrade with unmodified values and restarts pods.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant GithubAgent as GitHub Agent
  participant GithubAgents as CrewAI Crews
  participant MCPTools as MCP Tools
  User->>GithubAgent: submit GitHub query
  GithubAgent->>GithubAgents: extract query information
  GithubAgents->>MCPTools: invoke selected tools
  MCPTools-->>GithubAgents: return results
  GithubAgents-->>GithubAgent: return report
  GithubAgent-->>User: return response
Loading
sequenceDiagram
  participant Runner as UC-1 Runner
  participant Keycloak
  participant Controller
  participant OPA
  Runner->>Keycloak: authenticate user
  Runner->>Controller: onboard agent or tool
  Controller-->>Runner: return generated Rego
  Runner->>OPA: evaluate inbound policy
  OPA-->>Runner: return decision
  alt Inbound Allowed
    Runner->>Keycloak: exchange token
    Keycloak-->>Runner: return token
    Runner->>OPA: evaluate outbound intent
    OPA-->>Runner: return decision
  else Inbound Denied
    Runner->>Runner: stop
  end
Loading

Possibly related issues

Suggested reviewers: abigailgold

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a runnable Phase 1 UC-1 onboarding demo walkthrough for AIAC.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Correctness / agent behavior:
- prompts.py: stop mapping the read-only "sub-issues under #N" example to
  sub_issue_write; use issue_read only.
- prompts.py: instruct null (JSON) rather than the string "None" for absent
  extraction fields, so json.loads on the output succeeds.
- main.py: guard the crew task output before .raw so a task that produced no
  output returns a message instead of raising AttributeError.

Robustness (demo scripts/lib):
- setup_keycloak.py: send the full client representation on the token-exchange
  update_client PUT ({**client, ...}); a bare {"attributes": ...} clobbers the
  client's other fields.
- _lib.py: add a 30s timeout to the opa eval subprocess so a stalled opa aborts
  with a clear message instead of hanging the demo.
- show-state.py: abort when the prior diff snapshot is missing instead of
  silently diffing against empty sets.
- 00-prereqs.py: fix the step denominator ("of 8" -> "of 4").
- scenario.py: type Intent.function_name as str | None (the devops-user
  inbound-denial intent carries None).

Deployment / install:
- install.sh, INSTALL.md: build/load the demo images under the localhost/
  prefix to match the Deployment manifests' image refs (localhost/github-*:latest,
  IfNotPresent) and avoid ImagePullBackOff on the docker path.
- demo.md: drop the invalid 'make show --diff' form (make treats --diff as an
  option); keep only 'make diff PRIOR=...'.

opa-kind-enable.sh:
- derive the OPA bundle_url from $RELEASE_NAMESPACE instead of hardcoding
  rossoctl-system, matching where step 1 deploys bundle-service.
- add --wait --timeout 5m to helm upgrade so the release settles before the
  agent pods are restarted.
- track temp files and clean them on EXIT (podman tar no longer leaks on a
  failed kind load); use portable trailing-X mktemp templates.

a2a_agent.py: run MCPServerAdapter __enter__/__exit__ on a single dedicated
worker thread so the adapter's session lifecycle stays on one thread, rather
than asyncio.to_thread's shared pool which could split them across workers.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

🧹 Nitpick comments (5)
scripts/opa-kind-enable.sh (2)

54-60: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Validate the resolved container runtime.

If the user sets CONTAINER_RUNTIME to a runtime that is not installed, the script fails later at the build step with a shell "command not found" error. A single check after detection gives a clear message.

♻️ Proposed check
 else
   CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}"
 fi
+
+if ! command -v "$CONTAINER_RUNTIME" &> /dev/null; then
+  echo "ERROR: container runtime '${CONTAINER_RUNTIME}' not found in PATH" >&2
+  exit 1
+fi
🤖 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 `@scripts/opa-kind-enable.sh` around lines 54 - 60, After the container-runtime
detection conditional, validate the resolved CONTAINER_RUNTIME with command -v
before any build or runtime commands execute. If it is unavailable, emit a clear
error identifying the missing runtime and exit nonzero; preserve the existing
provider and default-selection behavior.

142-143: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Both scripts delete agent pods without waiting for the replacements. Each script runs kubectl delete pods -l rossoctl.io/type=agent and then prints its completion message. The replacement pods are still starting, so the printed verification command can read stale state, and a caller that chains the next demo step can hit AuthBridge sidecars that do not yet serve the intended pipeline. The shared root cause is one missing readiness wait after the label-based delete.

  • scripts/opa-kind-enable.sh#L142-L143: add kubectl wait --for=condition=Ready pods -n "$AGENT_NAMESPACE" -l rossoctl.io/type=agent --timeout=5m after the delete.
  • scripts/opa-kind-restore.sh#L50-L51: add the same kubectl wait call after the delete.
🤖 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 `@scripts/opa-kind-enable.sh` around lines 142 - 143, The agent pod restart
flows do not wait for replacement pods to become Ready. In
scripts/opa-kind-enable.sh lines 142-143, add kubectl wait --for=condition=Ready
pods -n "$AGENT_NAMESPACE" -l rossoctl.io/type=agent --timeout=5m immediately
after the pod deletion; apply the same change in scripts/opa-kind-restore.sh
lines 50-51 after its delete command.
aiac/demo/use-cases/uc1-onboarding/Makefile (1)

48-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

make diff without PRIOR passes a bare --diff flag.

If a user runs make diff with no PRIOR, the recipe expands to --diff with no value. Add a default so the target is self-describing.

♻️ Proposed default
+PRIOR     ?= 01-after-agent
+
 diff:  ## Show the latest snapshot, diffed against generated/<PRIOR> (e.g. make diff PRIOR=01-after-agent)
 	$(PYTHON) show-state.py --diff $(PRIOR)
🤖 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 `@aiac/demo/use-cases/uc1-onboarding/Makefile` around lines 48 - 49, Update the
diff target’s show-state.py invocation to supply a meaningful default value when
PRIOR is unset, so running make diff without arguments remains self-describing
while preserving the explicit PRIOR behavior.
aiac/demo/use-cases/uc1-onboarding/lib/_lib.py (1)

73-80: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

table() raises IndexError when rows is empty and headers is None.

Line 75 indexes all_rows[0]. Every current caller passes headers, so this is latent only. Add an early return to keep the helper safe.

♻️ Proposed guard
 def table(rows: list[tuple[str, ...]], headers: tuple[str, ...] | None = None) -> None:
     all_rows = ([headers] if headers else []) + rows
+    if not all_rows:
+        return
     widths = [max(len(str(r[i])) for r in all_rows) for i in range(len(all_rows[0]))]
🤖 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 `@aiac/demo/use-cases/uc1-onboarding/lib/_lib.py` around lines 73 - 80, Update
table() to return immediately when both rows and headers are absent, before
accessing all_rows[0]. Preserve the existing formatting behavior when headers or
data rows are available.
aiac/demo/use-cases/uc1-onboarding/demo.md (1)

12-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a language to the two fenced code blocks.

markdownlint reports MD040 for the policy block at line 12 and the architecture diagram at line 106. Use text for both.

Also applies to: 106-106

🤖 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 `@aiac/demo/use-cases/uc1-onboarding/demo.md` at line 12, Update the two fenced
code blocks in the demo documentation, including the policy block and
architecture diagram, to specify the text language after each opening fence. Use
text for both blocks to satisfy markdownlint MD040.

Source: Linters/SAST tools

🤖 Prompt for all review comments with 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.

Inline comments:
In `@aiac/CLAUDE.md`:
- Around line 25-27: Update the issue-label description in CLAUDE.md to use the
canonical aiac-status:<value> prefix instead of status:<value>, matching the
conventions documented in issue-tracker.md and triage-labels.md while preserving
the existing status-field description.

In `@aiac/demo/assets/agents/github_agent/.dockerignore`:
- Around line 3-4: Update the Docker ignore rules near the existing .env entry
to exclude all dotenv files using the .env* pattern, and re-include only a
non-secret template file if the build requires it.

In `@aiac/demo/assets/agents/github_agent/a2a_agent.py`:
- Around line 206-207: Update the MCP setup lifecycle around adapter.__enter__
and its ThreadPoolExecutor so coroutine cancellation cannot leave setup I/O
running without cleanup. Add cooperative cancellation for the blocked worker, or
move both __enter__ and __exit__ into an explicitly managed process/thread
execution context that guarantees cleanup before cancellation completes.

In `@aiac/demo/assets/agents/github_agent/Dockerfile`:
- Around line 12-13: Update the Dockerfile runtime setup to create a non-root
user with UID 10001, change the ownership target from 1001 to 10001, and run the
image as USER 10001. Keep AuthBridge-specific Kubernetes UID overrides separate
from this image default.

In `@aiac/demo/assets/agents/github_agent/README.md`:
- Around line 23-24: Use a repository-root path for the UC-1 stub in both
aiac/demo/assets/agents/github_agent/README.md lines 23-24 and
aiac/docs/specs/demo/github-agent.md line 221, changing the reference to
aiac/demo/assets/tools/github_tool/; keep both documents consistent.

In `@aiac/demo/assets/INSTALL.md`:
- Around line 48-55: Update the deployment guidance in
aiac/demo/assets/agents/github_agent/README.md:73-74 to clearly label the linked
INSTALL.md as discovery-only and not sufficient for agent execution, while
documenting the required github-tool-mcp prerequisite for execution. Leave
aiac/demo/assets/INSTALL.md:48-55 unchanged, since it correctly remains a
discovery-only install path.

In `@aiac/demo/assets/install.sh`:
- Around line 42-51: Update build_and_load() to use kind load image-archive when
detect_runtime() selects podman, saving the built image with $RUNTIME save and
passing that archive to kind; retain kind load docker-image for docker. Ensure
the Podman path follows the supported image-archive import flow.

In `@aiac/demo/assets/tools/github_tool/requirements.txt`:
- Around line 1-5: Update the dependency entries in requirements.txt to use
exact audited versions rather than lower and upper bounds, including pinning mcp
to the audited 1.9.4 release and explicitly pinning its vulnerable transitive
python-multipart dependency to a safe audited version. Pin uvicorn, httpx,
pytest, and pytest-asyncio as well, using the project’s approved versions.

In `@aiac/demo/assets/tools/github_tool/test/test_server.py`:
- Around line 26-28: Update test_returns_exactly_four_tool_names to assert that
the tools response length equals the expected number of TOOL_SCOPES entries
before comparing the name set, ensuring duplicate or additional entries cannot
pass the test.

In `@aiac/demo/use-cases/uc1-onboarding/init/00-prereqs.py`:
- Around line 120-122: Update the installation-complete check in 00-prereqs.py
at lines 120-122 to include aiac-policy-store and wait for both Deployments and
the StatefulSet to become ready before returning. Also update the install.sh
skip path at lines 151-153 to verify rollout readiness for both demo Deployments
before skipping installation.

In `@aiac/demo/use-cases/uc1-onboarding/lib/setup_keycloak.py`:
- Around line 54-75: Invoke ensure_default_audience_scope after the tool
onboarding flow creates the tool audience scope, using the agent client UUID and
tool audience scope name. Update the documented setup/onboard sequence or add
the call to the tool onboarding entry point, such as 04-onboard-tool.py, so
running make demo assigns the scope as a default on the agent client.

In `@aiac/demo/use-cases/uc1-onboarding/show-state.py`:
- Around line 69-79: Update the snapshot handling in show-state’s file
enumeration and grant_sets flow so incomplete snapshots do not reach grant_sets,
which expects both Rego files. Require both cfg.inbound_rego and
cfg.outbound_rego before evaluating grants; otherwise retain the available-file
output and skip grant evaluation without failing.
- Around line 87-103: Update show_diff to first render unified, line-oriented
diffs for the inbound and outbound Rego files between prior_dir and current_dir,
handling a missing current snapshot file consistently with the existing flow.
Keep the existing grant_sets comparison and added/removed summary after the
textual diffs so both policy changes and order-independent grant differences are
shown.

In `@aiac/docs/agents/domain.md`:
- Around line 21-31: Update the fenced tree block in domain.md to specify the
text language identifier, changing the opening fence to use text while
preserving the existing non-executable directory tree content.

In `@scripts/opa-kind-restore.sh`:
- Around line 46-48: Update the helm upgrade invocation in the restore flow of
opa-kind-restore.sh to include --wait with the existing 5-minute timeout,
ensuring the reverted pipeline configuration is reconciled before subsequent pod
deletion. Leave the existing values file and documented omission of --set flags
unchanged.

---

Nitpick comments:
In `@aiac/demo/use-cases/uc1-onboarding/demo.md`:
- Line 12: Update the two fenced code blocks in the demo documentation,
including the policy block and architecture diagram, to specify the text
language after each opening fence. Use text for both blocks to satisfy
markdownlint MD040.

In `@aiac/demo/use-cases/uc1-onboarding/lib/_lib.py`:
- Around line 73-80: Update table() to return immediately when both rows and
headers are absent, before accessing all_rows[0]. Preserve the existing
formatting behavior when headers or data rows are available.

In `@aiac/demo/use-cases/uc1-onboarding/Makefile`:
- Around line 48-49: Update the diff target’s show-state.py invocation to supply
a meaningful default value when PRIOR is unset, so running make diff without
arguments remains self-describing while preserving the explicit PRIOR behavior.

In `@scripts/opa-kind-enable.sh`:
- Around line 54-60: After the container-runtime detection conditional, validate
the resolved CONTAINER_RUNTIME with command -v before any build or runtime
commands execute. If it is unavailable, emit a clear error identifying the
missing runtime and exit nonzero; preserve the existing provider and
default-selection behavior.
- Around line 142-143: The agent pod restart flows do not wait for replacement
pods to become Ready. In scripts/opa-kind-enable.sh lines 142-143, add kubectl
wait --for=condition=Ready pods -n "$AGENT_NAMESPACE" -l rossoctl.io/type=agent
--timeout=5m immediately after the pod deletion; apply the same change in
scripts/opa-kind-restore.sh lines 50-51 after its delete command.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d01bc82-495a-41f2-a4bc-99ee2d9845db

📥 Commits

Reviewing files that changed from the base of the PR and between c5798c6 and 1b17ad5.

⛔ Files ignored due to path filters (1)
  • aiac/demo/assets/agents/github_agent/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (55)
  • aiac/.gitignore
  • aiac/CLAUDE.md
  • aiac/demo/assets/INSTALL.md
  • aiac/demo/assets/agents/github_agent/.dockerignore
  • aiac/demo/assets/agents/github_agent/.env.template
  • aiac/demo/assets/agents/github_agent/Dockerfile
  • aiac/demo/assets/agents/github_agent/README.md
  • aiac/demo/assets/agents/github_agent/a2a_agent.py
  • aiac/demo/assets/agents/github_agent/github_agent/__init__.py
  • aiac/demo/assets/agents/github_agent/github_agent/agents.py
  • aiac/demo/assets/agents/github_agent/github_agent/config.py
  • aiac/demo/assets/agents/github_agent/github_agent/data_types.py
  • aiac/demo/assets/agents/github_agent/github_agent/event.py
  • aiac/demo/assets/agents/github_agent/github_agent/llm.py
  • aiac/demo/assets/agents/github_agent/github_agent/main.py
  • aiac/demo/assets/agents/github_agent/github_agent/prompts.py
  • aiac/demo/assets/agents/github_agent/github_agent/tools.py
  • aiac/demo/assets/agents/github_agent/k8s/configmaps.yaml
  • aiac/demo/assets/agents/github_agent/k8s/github-agent-deployment.yaml
  • aiac/demo/assets/agents/github_agent/pyproject.toml
  • aiac/demo/assets/agents/github_agent/test/test_agent_card.py
  • aiac/demo/assets/agents/github_agent/test/test_prereq.py
  • aiac/demo/assets/agents/github_agent/test/test_tools.py
  • aiac/demo/assets/agents/github_agent/test_startup.exp
  • aiac/demo/assets/install.sh
  • aiac/demo/assets/tools/github_tool/Dockerfile
  • aiac/demo/assets/tools/github_tool/k8s/github-tool-deployment.yaml
  • aiac/demo/assets/tools/github_tool/pytest.ini
  • aiac/demo/assets/tools/github_tool/requirements.txt
  • aiac/demo/assets/tools/github_tool/server.py
  • aiac/demo/assets/tools/github_tool/test/__init__.py
  • aiac/demo/assets/tools/github_tool/test/conftest.py
  • aiac/demo/assets/tools/github_tool/test/test_server.py
  • aiac/demo/use-cases/.gitkeep
  • aiac/demo/use-cases/uc1-onboarding/Makefile
  • aiac/demo/use-cases/uc1-onboarding/demo.md
  • aiac/demo/use-cases/uc1-onboarding/init/00-prereqs.py
  • aiac/demo/use-cases/uc1-onboarding/init/01-clear.py
  • aiac/demo/use-cases/uc1-onboarding/init/02-setup.py
  • aiac/demo/use-cases/uc1-onboarding/lib/_lib.py
  • aiac/demo/use-cases/uc1-onboarding/lib/scenario.py
  • aiac/demo/use-cases/uc1-onboarding/lib/setup_keycloak.py
  • aiac/demo/use-cases/uc1-onboarding/onboard/03-onboard-agent.py
  • aiac/demo/use-cases/uc1-onboarding/onboard/04-onboard-tool.py
  • aiac/demo/use-cases/uc1-onboarding/run/run-developer.py
  • aiac/demo/use-cases/uc1-onboarding/run/run-devops.py
  • aiac/demo/use-cases/uc1-onboarding/run/run-tester.py
  • aiac/demo/use-cases/uc1-onboarding/show-state.py
  • aiac/docs/agents/domain.md
  • aiac/docs/agents/issue-tracker.md
  • aiac/docs/agents/triage-labels.md
  • aiac/docs/specs/demo/github-agent.md
  • aiac/docs/specs/demo/github-tool.md
  • scripts/opa-kind-enable.sh
  • scripts/opa-kind-restore.sh

Comment thread aiac/CLAUDE.md
Comment on lines +25 to +27
**Task**-typed leaf issues. Every issue carries the `aiac` label plus cumulative
`area:<path>` labels; open issues also carry a `status:<value>` label, and the
Project's **AIAC Status** field records the issue's status.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Use the canonical aiac-status:<value> label.

This paragraph says open issues carry status:<value>, but aiac/docs/agents/issue-tracker.md and aiac/docs/agents/triage-labels.md use aiac-status:<value>. Keep one prefix. Otherwise issue filters and triage commands can select different issues.

🤖 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 `@aiac/CLAUDE.md` around lines 25 - 27, Update the issue-label description in
CLAUDE.md to use the canonical aiac-status:<value> prefix instead of
status:<value>, matching the conventions documented in issue-tracker.md and
triage-labels.md while preserving the existing status-field description.

Comment on lines +206 to +207
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as mcp_exec:
mcp_tools = await loop.run_in_executor(mcp_exec, adapter.__enter__)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

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

cd aiac
.venv/bin/python - <<'PY'
import asyncio
import time
from concurrent.futures import ThreadPoolExecutor

async def blocked_scope():
    loop = asyncio.get_running_loop()
    with ThreadPoolExecutor(max_workers=1) as executor:
        await loop.run_in_executor(executor, time.sleep, 2)

async def main():
    task = asyncio.create_task(blocked_scope())
    await asyncio.sleep(0.1)

    started = time.monotonic()
    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        pass

    elapsed = time.monotonic() - started
    print(f"Cancellation completion time: {elapsed:.2f}s")
    assert elapsed >= 1.5, "Executor shutdown did not wait for the running worker"

asyncio.run(main())
PY

Repository: rossoctl/cortex

Length of output: 215


🏁 Script executed:

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

echo "Repository files around target:"
git ls-files | rg '(^|/)a2a_agent\.py$|(^|/)README\.md$|(^|/)pyproject\.toml$|(^|/)requirements|(^|/)poetry.lock$' || true

if [ -f aiac/demo/assets/agents/github_agent/a2a_agent.py ]; then
  echo
  echo "Target outline:"
  ast-grep outline aiac/demo/assets/agents/github_agent/a2a_agent.py --view condensed || true
  echo
  echo "Target lines 180-230:"
  sed -n '180,230p' aiac/demo/assets/agents/github_agent/a2a_agent.py | cat -n
fi

echo
echo "Python availability:"
for p in python python3 .venv/bin/python /usr/bin/python3; do
  if command -v "$p" >/dev/null 2>&1; then
    "$p" -V
  fi
done

Repository: rossoctl/cortex

Length of output: 4777


🏁 Script executed:

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

python3 - <<'PY'
import asyncio
import concurrent.futures
import inspect
import sys

print("Python", sys.version)

print("ThreadPoolExecutor shutdown signature:")
print(inspect.signature(concurrent.futures.ThreadPoolExecutor.shutdown))
print("ThreadPoolExecutor exit signature:")
try:
    method = concurrent.futures.ThreadPoolExecutor.__exit__
except Exception as e:
    print("Error:", type(e).__name__, e)
    method = None
if method:
    print(inspect.signature(method))

# Programmatic call-site check based on source strings:
# avoid importing/converting potentially repository code.
src = open("aiac/demo/assets/agents/github_agent/a2a_agent.py", encoding="utf-8").read()
checks = {
    "uses same executor for __enter__ and __exit__": "mcp_exec" in src[src.index("adapter = MCPServerAdapter"):] and
                                                 "adapter.__enter__" in src[src.index("adapter = MCPServerAdapter"):] and
                                                 "adapter.__exit__" in src[src.index("adapter = MCPServerAdapter"):] and
                                                 "ThreadPoolExecutor(max_workers=1)" in src[src.index("adapter = MCPServerAdapter"):],
    "executor is context-managed inside coroutine": "with concurrent.futures.ThreadPoolExecutor(max_workers=1) as mcp_exec:" in src,
}
for name, ok in checks.items():
    print(f"{name}: {ok}")
PY

Repository: rossoctl/cortex

Length of output: 493


🌐 Web query:

Python asyncio loop.run_in_executor cancellation run_in_executor shutdown ThreadPoolExecutor wait True

💡 Result:

In Python, calling.cancel on the asyncio.Future returned by loop.run_in_executor does not stop or terminate the underlying task executing in the ThreadPoolExecutor [1][2][3]. The cancellation is superficial: it only notifies the asyncio event loop that you are no longer interested in the result [3]. The task in the worker thread will continue to execute to completion [1][2][4]. Python threads cannot be safely interrupted or killed once they have started running [4][3]. To effectively stop a long-running blocking operation in a ThreadPoolExecutor, you must design your code to cooperate with a signaling mechanism [1][4][3]. Common patterns include: 1. Using a threading.Event: Pass a threading.Event object to your blocking function and periodically check it (e.g., if event.is_set: return). When you want to cancel, set the event [1][3]. 2. Cooperative cancellation: For longer tasks, structure them as a series of smaller, interruptible steps that check for a stop signal frequently [4]. Regarding ThreadPoolExecutor shutdown: - When you use a context manager (with ThreadPoolExecutor as executor:), the executor implicitly calls.shutdown(wait=True) upon exiting the block [5][6]. This causes the main thread to wait until all pending and currently running tasks in that executor are completed before continuing [4][5]. - If you cancel an asyncio task that is waiting on a result from an executor, the asyncio part of the task handles the cancellation, but the executor's worker thread is unaware and will keep running [2][4]. - To shut down an executor while tasks are still running, you may need to call executor.shutdown(wait=True) (or wait=False if you do not want to block) manually [5][6]. Note that shutdown with wait=True will block the calling thread until the executor finishes its tasks [5][6]. Shutdown with wait=False will allow the program to continue, but ensure resources are cleaned up later [5]. - Loop.shutdown_default_executor can be used to manage the cleanup of the default executor explicitly [7][8]. In summary, because standard Python threads cannot be forcefully terminated, cancellation must be implemented cooperatively within the function being executed [1][4].

Citations:


Do not rely on cancellation to stop MCP setup work.

loop.run_in_executor(..., adapter.__enter__) cannot be stopped by task.cancel() when adapter.__enter__ is already scheduled on the thread pool. The worker may still perform MCP connection I/O after the coroutine is cancelled, blocking the same-thread __exit__ cleanup or leaving the connection in a pending state. Add cooperative cancellation for blocked work, or move the whole lifecycle to an explicit process/thread pool where cleanup is guaranteed.

🤖 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 `@aiac/demo/assets/agents/github_agent/a2a_agent.py` around lines 206 - 207,
Update the MCP setup lifecycle around adapter.__enter__ and its
ThreadPoolExecutor so coroutine cancellation cannot leave setup I/O running
without cleanup. Add cooperative cancellation for the blocked worker, or move
both __enter__ and __exit__ into an explicitly managed process/thread execution
context that guarantees cleanup before cancellation completes.

Source: Coding guidelines

Comment thread aiac/demo/assets/agents/github_agent/README.md Outdated
Comment thread aiac/demo/assets/INSTALL.md
Comment thread aiac/demo/assets/install.sh
Comment thread aiac/demo/use-cases/uc1-onboarding/lib/setup_keycloak.py Outdated
Comment thread aiac/demo/use-cases/uc1-onboarding/show-state.py
Comment thread aiac/demo/use-cases/uc1-onboarding/show-state.py
Comment thread aiac/docs/agents/domain.md Outdated
Comment thread scripts/opa-kind-restore.sh Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 15

🧹 Nitpick comments (5)
scripts/opa-kind-enable.sh (2)

54-60: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Validate the resolved container runtime.

If the user sets CONTAINER_RUNTIME to a runtime that is not installed, the script fails later at the build step with a shell "command not found" error. A single check after detection gives a clear message.

♻️ Proposed check
 else
   CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}"
 fi
+
+if ! command -v "$CONTAINER_RUNTIME" &> /dev/null; then
+  echo "ERROR: container runtime '${CONTAINER_RUNTIME}' not found in PATH" >&2
+  exit 1
+fi
🤖 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 `@scripts/opa-kind-enable.sh` around lines 54 - 60, After the container-runtime
detection conditional, validate the resolved CONTAINER_RUNTIME with command -v
before any build or runtime commands execute. If it is unavailable, emit a clear
error identifying the missing runtime and exit nonzero; preserve the existing
provider and default-selection behavior.

142-143: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Both scripts delete agent pods without waiting for the replacements. Each script runs kubectl delete pods -l rossoctl.io/type=agent and then prints its completion message. The replacement pods are still starting, so the printed verification command can read stale state, and a caller that chains the next demo step can hit AuthBridge sidecars that do not yet serve the intended pipeline. The shared root cause is one missing readiness wait after the label-based delete.

  • scripts/opa-kind-enable.sh#L142-L143: add kubectl wait --for=condition=Ready pods -n "$AGENT_NAMESPACE" -l rossoctl.io/type=agent --timeout=5m after the delete.
  • scripts/opa-kind-restore.sh#L50-L51: add the same kubectl wait call after the delete.
🤖 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 `@scripts/opa-kind-enable.sh` around lines 142 - 143, The agent pod restart
flows do not wait for replacement pods to become Ready. In
scripts/opa-kind-enable.sh lines 142-143, add kubectl wait --for=condition=Ready
pods -n "$AGENT_NAMESPACE" -l rossoctl.io/type=agent --timeout=5m immediately
after the pod deletion; apply the same change in scripts/opa-kind-restore.sh
lines 50-51 after its delete command.
aiac/demo/use-cases/uc1-onboarding/Makefile (1)

48-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

make diff without PRIOR passes a bare --diff flag.

If a user runs make diff with no PRIOR, the recipe expands to --diff with no value. Add a default so the target is self-describing.

♻️ Proposed default
+PRIOR     ?= 01-after-agent
+
 diff:  ## Show the latest snapshot, diffed against generated/<PRIOR> (e.g. make diff PRIOR=01-after-agent)
 	$(PYTHON) show-state.py --diff $(PRIOR)
🤖 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 `@aiac/demo/use-cases/uc1-onboarding/Makefile` around lines 48 - 49, Update the
diff target’s show-state.py invocation to supply a meaningful default value when
PRIOR is unset, so running make diff without arguments remains self-describing
while preserving the explicit PRIOR behavior.
aiac/demo/use-cases/uc1-onboarding/lib/_lib.py (1)

73-80: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

table() raises IndexError when rows is empty and headers is None.

Line 75 indexes all_rows[0]. Every current caller passes headers, so this is latent only. Add an early return to keep the helper safe.

♻️ Proposed guard
 def table(rows: list[tuple[str, ...]], headers: tuple[str, ...] | None = None) -> None:
     all_rows = ([headers] if headers else []) + rows
+    if not all_rows:
+        return
     widths = [max(len(str(r[i])) for r in all_rows) for i in range(len(all_rows[0]))]
🤖 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 `@aiac/demo/use-cases/uc1-onboarding/lib/_lib.py` around lines 73 - 80, Update
table() to return immediately when both rows and headers are absent, before
accessing all_rows[0]. Preserve the existing formatting behavior when headers or
data rows are available.
aiac/demo/use-cases/uc1-onboarding/demo.md (1)

12-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a language to the two fenced code blocks.

markdownlint reports MD040 for the policy block at line 12 and the architecture diagram at line 106. Use text for both.

Also applies to: 106-106

🤖 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 `@aiac/demo/use-cases/uc1-onboarding/demo.md` at line 12, Update the two fenced
code blocks in the demo documentation, including the policy block and
architecture diagram, to specify the text language after each opening fence. Use
text for both blocks to satisfy markdownlint MD040.

Source: Linters/SAST tools

🤖 Prompt for all review comments with 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.

Inline comments:
In `@aiac/CLAUDE.md`:
- Around line 25-27: Update the issue-label description in CLAUDE.md to use the
canonical aiac-status:<value> prefix instead of status:<value>, matching the
conventions documented in issue-tracker.md and triage-labels.md while preserving
the existing status-field description.

In `@aiac/demo/assets/agents/github_agent/.dockerignore`:
- Around line 3-4: Update the Docker ignore rules near the existing .env entry
to exclude all dotenv files using the .env* pattern, and re-include only a
non-secret template file if the build requires it.

In `@aiac/demo/assets/agents/github_agent/a2a_agent.py`:
- Around line 206-207: Update the MCP setup lifecycle around adapter.__enter__
and its ThreadPoolExecutor so coroutine cancellation cannot leave setup I/O
running without cleanup. Add cooperative cancellation for the blocked worker, or
move both __enter__ and __exit__ into an explicitly managed process/thread
execution context that guarantees cleanup before cancellation completes.

In `@aiac/demo/assets/agents/github_agent/Dockerfile`:
- Around line 12-13: Update the Dockerfile runtime setup to create a non-root
user with UID 10001, change the ownership target from 1001 to 10001, and run the
image as USER 10001. Keep AuthBridge-specific Kubernetes UID overrides separate
from this image default.

In `@aiac/demo/assets/agents/github_agent/README.md`:
- Around line 23-24: Use a repository-root path for the UC-1 stub in both
aiac/demo/assets/agents/github_agent/README.md lines 23-24 and
aiac/docs/specs/demo/github-agent.md line 221, changing the reference to
aiac/demo/assets/tools/github_tool/; keep both documents consistent.

In `@aiac/demo/assets/INSTALL.md`:
- Around line 48-55: Update the deployment guidance in
aiac/demo/assets/agents/github_agent/README.md:73-74 to clearly label the linked
INSTALL.md as discovery-only and not sufficient for agent execution, while
documenting the required github-tool-mcp prerequisite for execution. Leave
aiac/demo/assets/INSTALL.md:48-55 unchanged, since it correctly remains a
discovery-only install path.

In `@aiac/demo/assets/install.sh`:
- Around line 42-51: Update build_and_load() to use kind load image-archive when
detect_runtime() selects podman, saving the built image with $RUNTIME save and
passing that archive to kind; retain kind load docker-image for docker. Ensure
the Podman path follows the supported image-archive import flow.

In `@aiac/demo/assets/tools/github_tool/requirements.txt`:
- Around line 1-5: Update the dependency entries in requirements.txt to use
exact audited versions rather than lower and upper bounds, including pinning mcp
to the audited 1.9.4 release and explicitly pinning its vulnerable transitive
python-multipart dependency to a safe audited version. Pin uvicorn, httpx,
pytest, and pytest-asyncio as well, using the project’s approved versions.

In `@aiac/demo/assets/tools/github_tool/test/test_server.py`:
- Around line 26-28: Update test_returns_exactly_four_tool_names to assert that
the tools response length equals the expected number of TOOL_SCOPES entries
before comparing the name set, ensuring duplicate or additional entries cannot
pass the test.

In `@aiac/demo/use-cases/uc1-onboarding/init/00-prereqs.py`:
- Around line 120-122: Update the installation-complete check in 00-prereqs.py
at lines 120-122 to include aiac-policy-store and wait for both Deployments and
the StatefulSet to become ready before returning. Also update the install.sh
skip path at lines 151-153 to verify rollout readiness for both demo Deployments
before skipping installation.

In `@aiac/demo/use-cases/uc1-onboarding/lib/setup_keycloak.py`:
- Around line 54-75: Invoke ensure_default_audience_scope after the tool
onboarding flow creates the tool audience scope, using the agent client UUID and
tool audience scope name. Update the documented setup/onboard sequence or add
the call to the tool onboarding entry point, such as 04-onboard-tool.py, so
running make demo assigns the scope as a default on the agent client.

In `@aiac/demo/use-cases/uc1-onboarding/show-state.py`:
- Around line 69-79: Update the snapshot handling in show-state’s file
enumeration and grant_sets flow so incomplete snapshots do not reach grant_sets,
which expects both Rego files. Require both cfg.inbound_rego and
cfg.outbound_rego before evaluating grants; otherwise retain the available-file
output and skip grant evaluation without failing.
- Around line 87-103: Update show_diff to first render unified, line-oriented
diffs for the inbound and outbound Rego files between prior_dir and current_dir,
handling a missing current snapshot file consistently with the existing flow.
Keep the existing grant_sets comparison and added/removed summary after the
textual diffs so both policy changes and order-independent grant differences are
shown.

In `@aiac/docs/agents/domain.md`:
- Around line 21-31: Update the fenced tree block in domain.md to specify the
text language identifier, changing the opening fence to use text while
preserving the existing non-executable directory tree content.

In `@scripts/opa-kind-restore.sh`:
- Around line 46-48: Update the helm upgrade invocation in the restore flow of
opa-kind-restore.sh to include --wait with the existing 5-minute timeout,
ensuring the reverted pipeline configuration is reconciled before subsequent pod
deletion. Leave the existing values file and documented omission of --set flags
unchanged.

---

Nitpick comments:
In `@aiac/demo/use-cases/uc1-onboarding/demo.md`:
- Line 12: Update the two fenced code blocks in the demo documentation,
including the policy block and architecture diagram, to specify the text
language after each opening fence. Use text for both blocks to satisfy
markdownlint MD040.

In `@aiac/demo/use-cases/uc1-onboarding/lib/_lib.py`:
- Around line 73-80: Update table() to return immediately when both rows and
headers are absent, before accessing all_rows[0]. Preserve the existing
formatting behavior when headers or data rows are available.

In `@aiac/demo/use-cases/uc1-onboarding/Makefile`:
- Around line 48-49: Update the diff target’s show-state.py invocation to supply
a meaningful default value when PRIOR is unset, so running make diff without
arguments remains self-describing while preserving the explicit PRIOR behavior.

In `@scripts/opa-kind-enable.sh`:
- Around line 54-60: After the container-runtime detection conditional, validate
the resolved CONTAINER_RUNTIME with command -v before any build or runtime
commands execute. If it is unavailable, emit a clear error identifying the
missing runtime and exit nonzero; preserve the existing provider and
default-selection behavior.
- Around line 142-143: The agent pod restart flows do not wait for replacement
pods to become Ready. In scripts/opa-kind-enable.sh lines 142-143, add kubectl
wait --for=condition=Ready pods -n "$AGENT_NAMESPACE" -l rossoctl.io/type=agent
--timeout=5m immediately after the pod deletion; apply the same change in
scripts/opa-kind-restore.sh lines 50-51 after its delete command.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d01bc82-495a-41f2-a4bc-99ee2d9845db

📥 Commits

Reviewing files that changed from the base of the PR and between c5798c6 and 1b17ad5.

⛔ Files ignored due to path filters (1)
  • aiac/demo/assets/agents/github_agent/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (55)
  • aiac/.gitignore
  • aiac/CLAUDE.md
  • aiac/demo/assets/INSTALL.md
  • aiac/demo/assets/agents/github_agent/.dockerignore
  • aiac/demo/assets/agents/github_agent/.env.template
  • aiac/demo/assets/agents/github_agent/Dockerfile
  • aiac/demo/assets/agents/github_agent/README.md
  • aiac/demo/assets/agents/github_agent/a2a_agent.py
  • aiac/demo/assets/agents/github_agent/github_agent/__init__.py
  • aiac/demo/assets/agents/github_agent/github_agent/agents.py
  • aiac/demo/assets/agents/github_agent/github_agent/config.py
  • aiac/demo/assets/agents/github_agent/github_agent/data_types.py
  • aiac/demo/assets/agents/github_agent/github_agent/event.py
  • aiac/demo/assets/agents/github_agent/github_agent/llm.py
  • aiac/demo/assets/agents/github_agent/github_agent/main.py
  • aiac/demo/assets/agents/github_agent/github_agent/prompts.py
  • aiac/demo/assets/agents/github_agent/github_agent/tools.py
  • aiac/demo/assets/agents/github_agent/k8s/configmaps.yaml
  • aiac/demo/assets/agents/github_agent/k8s/github-agent-deployment.yaml
  • aiac/demo/assets/agents/github_agent/pyproject.toml
  • aiac/demo/assets/agents/github_agent/test/test_agent_card.py
  • aiac/demo/assets/agents/github_agent/test/test_prereq.py
  • aiac/demo/assets/agents/github_agent/test/test_tools.py
  • aiac/demo/assets/agents/github_agent/test_startup.exp
  • aiac/demo/assets/install.sh
  • aiac/demo/assets/tools/github_tool/Dockerfile
  • aiac/demo/assets/tools/github_tool/k8s/github-tool-deployment.yaml
  • aiac/demo/assets/tools/github_tool/pytest.ini
  • aiac/demo/assets/tools/github_tool/requirements.txt
  • aiac/demo/assets/tools/github_tool/server.py
  • aiac/demo/assets/tools/github_tool/test/__init__.py
  • aiac/demo/assets/tools/github_tool/test/conftest.py
  • aiac/demo/assets/tools/github_tool/test/test_server.py
  • aiac/demo/use-cases/.gitkeep
  • aiac/demo/use-cases/uc1-onboarding/Makefile
  • aiac/demo/use-cases/uc1-onboarding/demo.md
  • aiac/demo/use-cases/uc1-onboarding/init/00-prereqs.py
  • aiac/demo/use-cases/uc1-onboarding/init/01-clear.py
  • aiac/demo/use-cases/uc1-onboarding/init/02-setup.py
  • aiac/demo/use-cases/uc1-onboarding/lib/_lib.py
  • aiac/demo/use-cases/uc1-onboarding/lib/scenario.py
  • aiac/demo/use-cases/uc1-onboarding/lib/setup_keycloak.py
  • aiac/demo/use-cases/uc1-onboarding/onboard/03-onboard-agent.py
  • aiac/demo/use-cases/uc1-onboarding/onboard/04-onboard-tool.py
  • aiac/demo/use-cases/uc1-onboarding/run/run-developer.py
  • aiac/demo/use-cases/uc1-onboarding/run/run-devops.py
  • aiac/demo/use-cases/uc1-onboarding/run/run-tester.py
  • aiac/demo/use-cases/uc1-onboarding/show-state.py
  • aiac/docs/agents/domain.md
  • aiac/docs/agents/issue-tracker.md
  • aiac/docs/agents/triage-labels.md
  • aiac/docs/specs/demo/github-agent.md
  • aiac/docs/specs/demo/github-tool.md
  • scripts/opa-kind-enable.sh
  • scripts/opa-kind-restore.sh
🛑 Comments failed to post (4)
aiac/demo/assets/agents/github_agent/.dockerignore (1)

3-4: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Ignore all dotenv files from the build context.

The Dockerfile uses COPY . ., but this file excludes only .env. Credentials in .env.ollama, .env.openai, or .env.claude can therefore be copied into the image. Use .env* and re-include only a non-secret template if required.

Proposed ignore rule
-.env
+.env*
📝 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.

# Secrets — never bake into the image
.env*
🤖 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 `@aiac/demo/assets/agents/github_agent/.dockerignore` around lines 3 - 4,
Update the Docker ignore rules near the existing .env entry to exclude all
dotenv files using the .env* pattern, and re-include only a non-secret template
file if the build requires it.
aiac/demo/assets/agents/github_agent/Dockerfile (1)

12-13: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Run the image as UID 10001.

The Dockerfile defaults to UID 1001 and does not create the runtime user. The repository contract requires AIAC service images to create and run as UID 10001. Keep any AuthBridge-specific Kubernetes override separate from the image default.

Proposed Dockerfile change
-RUN chown -R 1001:1001 /app
-USER 1001
+RUN useradd --no-create-home --uid 10001 aiac \
+    && chown -R 10001:10001 /app
+USER 10001

As per coding guidelines, aiac/**/Dockerfile images must create and run as non-root UID 10001.

📝 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.

RUN useradd --no-create-home --uid 10001 aiac \
    && chown -R 10001:10001 /app
USER 10001
🤖 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 `@aiac/demo/assets/agents/github_agent/Dockerfile` around lines 12 - 13, Update
the Dockerfile runtime setup to create a non-root user with UID 10001, change
the ownership target from 1001 to 10001, and run the image as USER 10001. Keep
AuthBridge-specific Kubernetes UID overrides separate from this image default.

Source: Coding guidelines

aiac/demo/assets/tools/github_tool/requirements.txt (1)

1-5: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

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

echo "== requirements file =="
sed -n '1,40p' aiac/demo/assets/tools/github_tool/requirements.txt

echo
echo "== package versions from OSV =="
python3 - <<'PY'
import json, urllib.request

for pkg, ver in [("mcp", "1.9.4"), ("python-multipart", "0.0.9")]:
    data = json.dumps({
        "version": ver,
        "package": {"name": pkg, "ecosystem": "PyPI"}
    }).encode()
    req = urllib.request.Request(
        "https://api.osv.dev/v1/query",
        data=data,
        headers={"Content-Type": "application/json"}
    )
    with urllib.request.urlopen(req, timeout=20) as resp:
        body = json.load(resp)
    print(f"-- {pkg}=={ver}")
    print(json.dumps(body, indent=2, sort_keys=True))
PY

echo
echo "== optional resolver metadata with pip (if available) =="
python3 - <<'PY'
import json, urllib.request

def fetch_json(url):
    req = urllib.request.Request(url, headers={"User-Agent":"read-only-verification"})
    with urllib.request.urlopen(req, timeout=20) as resp:
        return json.load(resp)

for pkg in ("mcp", "python-multipart"):
    print(f"-- {pkg}")
    try:
        info = fetch_json(f"https://pypi.org/pypi/{pkg}/json")
        print("latest:", info["info"].get("version"))
        print("requires_dist:")
        for r in info["info"].get("requires_dist") or []:
            print(r)
        # If a resolver is available in the sandbox without installing repo code, show one resolution of the exact ranges.
    except Exception as e:
        print("pypi fetch failed:", e)
PY

Repository: rossoctl/cortex

Length of output: 2593


🏁 Script executed:

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

echo "== dependency resolver metadata with pip (if available) =="
python3 - <<'PY'
import json, urllib.request, sys, subprocess, tempfile, os

def fetch_json(url):
    req = urllib.request.Request(url, headers={"User-Agent":"read-only-verification"})
    with urllib.request.urlopen(req, timeout=20) as resp:
        return json.load(resp)

requirements_path = "aiac/demo/assets/tools/github_tool/requirements.txt"
for pkg in ("mcp", "uvicorn", "httpx", "pytest", "pytest-asyncio"):
    print(f"-- {pkg}")
    try:
        info = fetch_json(f"https://pypi.org/pypi/{pkg}/json")
        print("version:", info["info"].get("version"))
        print("requires_dist:")
        for r in info["info"].get("requires_dist") or []:
            print(r)
    except Exception as e:
        print("pypi fetch failed:", e)

print("\n-- requirements to resolve:")
with open(requirements_path, encoding="utf-8") as f:
    print(f.read())

requirements_content = open(requirements_path, encoding="utf-8").read()
with tempfile.TemporaryDirectory(prefix="cr-resolve-") as td:
    req_path = os.path.join(td, "requirements.txt")
    with open(req_path, "w", encoding="utf-8") as f:
        f.write(requirements_content)
    try:
        result = subprocess.run(
            [sys.executable, "-m", "pip", "install", "--dry-run", "--ignore-installed", "--no-deps", "-r", req_path],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            timeout=180,
        )
        print("dry-run status:", result.returncode)
        for line in result.stdout.splitlines():
            if any(s in line for s in ("Would install", "Collecting")):
                print(line)
        print("\nstderr sample:")
        for line in (result.stderr or "").splitlines()[-20:]:
            print(line)
    except Exception as e:
        print("pip dry-run unavailable or failed:", e)
PY

echo
echo "== pip freeze for installed resolver if available =="
python3 - <<'PY'
import pkg_resources, sys
print("pkg_resources version:", pkg_resources.get_distribution("setuptools").version if "pkg_resources" in sys.modules else "n/a")
print("pip version:", pkg_resources.get_distribution("pip").version)
PY

echo
echo "== exact mcp 1.9.4 dependencies if metadata is available =="
python3 - <<'PY'
import urllib.request, json
url = "https://pypi.org/pypi/mcp/1.9.4/json"
req = urllib.request.Request(url, headers={"User-Agent":"read-only-verification"})
try:
    with urllib.request.urlopen(req, timeout=20) as resp:
        info = json.load(resp)
    print("mcp==1.9.4 required by this:", any(r.startswith("mcp  (<2,") for r in info["info"].get("requires_dist") or []))
    for i, r in enumerate(info["info"].get("requires_dist") or []):
        if "python-multipart" in r:
            print("mcp==1.9.4 dependency mentions python-multipart:", r)
            break
    else:
        print("mcp==1.9.4 dependency mentions python-multipart: None")
except Exception as e:
    print("fetch failed:", e)
PY

Repository: rossoctl/cortex

Length of output: 1415


🌐 Web query:

OSV vulnerability mcp python 1.9.4

💡 Result:

The Python package mcp (the Model Context Protocol SDK) has been associated with specific security vulnerabilities that were addressed in version 1.9.4. A primary vulnerability, tracked as CVE-2025-53366 (also referenced as GHSA-3qhf-m339-9g5v and PYSEC-2026-1616), involved a validation error in the FastMCP Server [1][2][3]. This flaw could allow an unhandled exception when processing malformed requests, leading to a denial of service (DoS) where the server would return 500 errors and require a manual restart [1][4][5]. Version 1.9.4 includes the fix for this issue [1][3][5]. Additionally, another vulnerability related to an unhandled exception in the streamable HTTP transport (GHSA-j975-95f5-7wqh / PYSEC-2026-1618) was identified, which also leads to a denial of service [6][7]. This issue affects versions prior to 1.10.0 [6][7]. It is recommended to upgrade to at least version 1.9.4 (and ideally to the latest version) to mitigate these risks [5]. You can upgrade using: pip install --upgrade "mcp>=1.9.4" [5] Please note that search results also returned various independent projects named "OSV-MCP" or similar (e.g., StacklokLabs/osv-mcp, EdenYavin/OSV-MCP), which are distinct, third-party MCP servers designed to query the OSV (Open Source Vulnerabilities) database and are not the mcp SDK itself [8][9][10][11].

Citations:


🌐 Web query:

OSV vulnerability python-multipart 0.0.9

💡 Result:

Version 0.0.9 of python-multipart is affected by multiple high-severity and critical vulnerabilities, as it precedes several security patches released to address parsing, denial-of-service, and path traversal issues. Users are strongly advised to upgrade to the latest version of the library. The following vulnerabilities affect version 0.0.9: 1. Parameter Smuggling (CVE-2026-53538 / GHSA-6jv3-5f52-599m): The library incorrectly treated the semicolon (;) as a field separator in application/x-www-form-urlencoded bodies, inconsistent with the WHATWG URL standard. This allows attackers to smuggle form fields past upstream validators. Fixed in 0.0.30 [1][2][3]. 2. Memory Exhaustion via Negative Content-Length (CVE-2026-53540 / GHSA-v9pg-7xvm-68hf): The parse_form function failed to validate the Content-Length header, where a negative value caused the library to read the entire request body into memory. Fixed in 0.0.31 [4][5]. 3. Denial of Service (DoS) via Unbounded Multipart Headers (GHSA-pp6c-gr5w-3c5g): The parser lacked limits on the number or size of multipart part headers, allowing CPU exhaustion through crafted requests. Fixed in 0.0.27 [6]. 4. DoS via Large Multipart Preamble/Epilogue (CVE-2026-40347 / GHSA-mj87-hwqh-73pj): The library was vulnerable to DoS when processing crafted requests with excessively large preamble or epilogue sections. Fixed in 0.0.26 [7]. 5. DoS via Boundary Deformation (CVE-2024-53981 / GHSA-59g5-xgcq-4qw3): Excessive logging and CPU load could be triggered by malformed multipart boundaries. Fixed in 0.0.18 [8][9][10]. 6. Path Traversal (CVE-2026-24486): A vulnerability existed when using non-default configurations (specifically UPLOAD_DIR and UPLOAD_KEEP_FILENAME=True), allowing arbitrary file writes. Fixed in 0.0.22 [11]. Given these cumulative security risks, users should update to the most recent stable release of python-multipart to ensure all these issues are mitigated. [1][6][11][4][8][7]

Citations:


🌐 Web query:

Pypi mcp 1.9.4 dependencies python-multipart

💡 Result:

In the mcp package version 1.9.4 on PyPI, the dependency requirement for python-multipart is defined as >=0.0.9 [1][2]. While this requirement is officially specified in the project's metadata [1], it has been noted by community members that this version floor allows the installation of older versions of python-multipart that may be affected by known security vulnerabilities [3]. Consequently, some downstream users choose to enforce a higher minimum version (e.g., >=0.0.20 or newer) in their own project configurations to mitigate these risks [3].

Citations:


Pin audited dependency versions.

Use exact pins in aiac/demo/assets/tools/github_tool/requirements.txt so builds do not select vulnerable releases. mcp<2 can select 1.9.4, and mcp==1.9.4 allows python-multipart>=0.0.9, which includes known high-severity multipart parser vulnerabilities.

🧰 Tools
🪛 OSV Scanner (2.4.0)

[HIGH] 1-1: mcp 1.9.4: Model Context Protocol (MCP) Python SDK does not enable DNS rebinding protection by default

(PYSEC-2026-1617)


[HIGH] 1-1: mcp 1.9.4: MCP Python SDK has Unhandled Exception in Streamable HTTP Transport, Leading to Denial of Service

(PYSEC-2026-1618)


[HIGH] 1-1: mcp 1.9.4: MCP Python SDK: HTTP transports serve session requests without verifying the authenticated principal

(PYSEC-2026-3482)


[HIGH] 1-1: mcp 1.9.4: MCP Python SDK: WebSocket server transport does not support Host/Origin validation

(PYSEC-2026-3483)


[HIGH] 1-1: mcp 1.9.4: Model Context Protocol (MCP) Python SDK does not enable DNS rebinding protection by default

(GHSA-9h52-p55h-vw2f)


[HIGH] 1-1: mcp 1.9.4: MCP Python SDK has Unhandled Exception in Streamable HTTP Transport, Leading to Denial of Service

(GHSA-j975-95f5-7wqh)


[HIGH] 1-1: mcp 1.9.4: MCP Python SDK: HTTP transports serve session requests without verifying the authenticated principal

(GHSA-jpw9-pfvf-9f58)


[HIGH] 1-1: mcp 1.9.4: MCP Python SDK: WebSocket server transport does not support Host/Origin validation

(GHSA-vj7q-gjh5-988w)


[HIGH] 1-1: python-multipart 0.0.9: Denial of service (DoS) via deformation multipart/form-data boundary

(PYSEC-2026-1851)


[HIGH] 1-1: python-multipart 0.0.9: Python-Multipart has Arbitrary File Write via Non-Default Configuration

(PYSEC-2026-1852)


[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Quadratic-time querystring parsing with semicolon separators causes CPU denial of service

(PYSEC-2026-3036)


[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Semicolon treated as querystring field separator enables parameter smuggling

(PYSEC-2026-3037)


[HIGH] 1-1: python-multipart 0.0.9: python-multipart affected by Denial of Service via large multipart preamble or epilogue data

(PYSEC-2026-3038)


[HIGH] 1-1: python-multipart 0.0.9: python-multipart has Denial of Service via unbounded multipart part headers

(PYSEC-2026-3039)


[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Negative Content-Length in parse_form buffers the entire body in memory

(PYSEC-2026-3040)


[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Content-Disposition parameter smuggling via RFC 2231/5987 extended parameters

(PYSEC-2026-3041)


[HIGH] 1-1: python-multipart 0.0.9: Denial of service (DoS) via deformation multipart/form-data boundary

(GHSA-59g5-xgcq-4qw3)


[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Quadratic-time querystring parsing with semicolon separators causes CPU denial of service

(GHSA-5rvq-cxj2-64vf)


[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Semicolon treated as querystring field separator enables parameter smuggling

(GHSA-6jv3-5f52-599m)


[HIGH] 1-1: python-multipart 0.0.9: python-multipart affected by Denial of Service via large multipart preamble or epilogue data

(GHSA-mj87-hwqh-73pj)


[HIGH] 1-1: python-multipart 0.0.9: python-multipart has Denial of Service via unbounded multipart part headers

(GHSA-pp6c-gr5w-3c5g)


[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Negative Content-Length in parse_form buffers the entire body in memory

(GHSA-v9pg-7xvm-68hf)


[HIGH] 1-1: python-multipart 0.0.9: python-multipart: Content-Disposition parameter smuggling via RFC 2231/5987 extended parameters

(GHSA-vffw-93wf-4j4q)


[HIGH] 1-1: python-multipart 0.0.9: Python-Multipart has Arbitrary File Write via Non-Default Configuration

(GHSA-wp53-j4wj-2cfg)

🤖 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 `@aiac/demo/assets/tools/github_tool/requirements.txt` around lines 1 - 5,
Update the dependency entries in requirements.txt to use exact audited versions
rather than lower and upper bounds, including pinning mcp to the audited 1.9.4
release and explicitly pinning its vulnerable transitive python-multipart
dependency to a safe audited version. Pin uvicorn, httpx, pytest, and
pytest-asyncio as well, using the project’s approved versions.

Sources: Coding guidelines, Linters/SAST tools

aiac/demo/assets/tools/github_tool/test/test_server.py (1)

26-28: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the response count.

Set comparison accepts duplicate tool names. A response with an additional duplicate tool passes this test. Assert the list length before comparing the names.

Proposed fix
 def test_returns_exactly_four_tool_names(self, client):
     tools = _tools_list(client)
+    assert len(tools) == len(TOOL_SCOPES)
     assert {t["name"] for t in tools} == set(TOOL_SCOPES.keys())
📝 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.

    def test_returns_exactly_four_tool_names(self, client):
        tools = _tools_list(client)
        assert len(tools) == len(TOOL_SCOPES)
        assert {t["name"] for t in tools} == set(TOOL_SCOPES.keys())
🤖 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 `@aiac/demo/assets/tools/github_tool/test/test_server.py` around lines 26 - 28,
Update test_returns_exactly_four_tool_names to assert that the tools response
length equals the expected number of TOOL_SCOPES entries before comparing the
name set, ensuring duplicate or additional entries cannot pass the test.

@abigailgold abigailgold self-assigned this Aug 4, 2026
@abigailgold

Copy link
Copy Markdown

Issues found

# File Severity Comment
1 aiac/CLAUDE.md, aiac/docs/agents/issue-tracker.md, aiac/docs/agents/domain.md, aiac/docs/agents/triage-labels.md Must-verify All new/updated gh command examples and the Project link point at s-and-p-team/cortex (org s-and-p-team, repo s-and-p-team/cortex), but s-and-p-team/cortex is a fork of this repo (rossoctl/cortex is the canonical upstream this PR is opened against; s-and-p-team/cortex's parent/source is rossoctl/cortex). If this is not deliberate (e.g. the team genuinely tracks issues on the fork for some organizational reason), every gh issue create/list/view/edit example in these docs — and any AIAC agent-skill automation that follows them — will read/write issues on the wrong repository. Worth an explicit confirmation from the author before merge; if intentional, a one-line note explaining why the fork (not upstream) hosts the issue tracker would preempt future confusion.
2 aiac/demo/use-cases/uc1-onboarding/lib/scenario.py Nit USER_PASSWORD = "password" is a fixed, shared plaintext credential for the demo's Keycloak users. Low risk since this is a strictly-live, ephemeral-cluster demo (not a value used in production paths) and the file's own docstring frames it as test-only scenario data, but a one-line comment noting "demo-only credential, not used outside ephemeral local/CI clusters" would make the intent explicit to a future reader who greps for hardcoded passwords.
3 scripts/opa-kind-enable.sh (final step) Nit kubectl delete pods -n "$AGENT_NAMESPACE" -l rossoctl.io/type=agent runs unconditionally with no check for whether any pods match the selector. Not a bug (kubectl no-ops cleanly on zero matches) but worth a --ignore-not-found-style comment or check if this script is ever run against a namespace that may not have agent pods yet.

@oblinder
oblinder requested a review from abigailgold August 4, 2026 10:16
@oblinder
oblinder marked this pull request as draft August 4, 2026 10:17
- install.sh: load images into kind via image-archive under podman
  (kind load docker-image only works with docker).
- 00-prereqs.py: wait for both demo deployments to roll out on the
  already-deployed path, so "already deployed" also means "actually up".
- 04-onboard-tool.py: assign the tool's *-aud default scope to the agent
  client now that the tool (and its scope) exist — 02-setup runs too early
  for this and hits the skip branch.
- show-state.py: guard grant tallies behind a complete-snapshot check
  (grant_sets loads both Rego files) and render the line-oriented Rego
  text diff the module docstring promises before the semantic set diff.
- opa-kind-restore.sh: --wait --timeout 5m so the helm upgrade blocks on
  readiness before the sidecar restart.
- Docs: aiac/-prefix two github_tool path references for consistency with
  surrounding runnable-command paths; add text language to a fenced block.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
- #1: Note that tracking issues on the s-and-p-team/cortex fork (rather
  than upstream rossoctl/cortex) is deliberate, in issue-tracker.md and
  aiac/CLAUDE.md, so the -R scoping on gh examples isn't mistaken for a
  wrong-slug copy-paste.
- #2: Comment USER_PASSWORD in scenario.py as a demo-only credential for
  ephemeral local/CI clusters, never a production/secret value.
- #3: Add --ignore-not-found to the agent-pod restart in
  scripts/opa-kind-enable.sh so it no-ops cleanly on zero matches.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
@oblinder

oblinder commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @abigailgold — addressed all three in 88603a1.

# Resolution
1 Confirmed deliberate. `s-and-p-team/cortex` is the AIAC team's fork of upstream `rossoctl/cortex` (its `parent`/`source` is this repo), and AIAC work is tracked on the fork while PRs target upstream. Added an explicit "this is deliberate" note in `aiac/docs/agents/issue-tracker.md` (the canonical source the skills read) and `aiac/CLAUDE.md`, so the `-R s-and-p-team/cortex` scoping isn't mistaken for a wrong-slug copy-paste. Left `triage-labels.md`/`domain.md` untouched — the former defers to issue-tracker.md, the latter has no repo-scoped `gh` commands.
2 Added a one-line comment on `USER_PASSWORD` in `scenario.py` marking it demo-only for ephemeral local/CI clusters, never a production/secret value. No `# nosec`/`# noqa` needed — Bandit scans only `authbridge`/`tests` and ruff selects only `E,F,I,W`, so neither flags this path.
3 Added `--ignore-not-found` (plus a comment) to the agent-pod restart in `scripts/opa-kind-enable.sh` so it no-ops cleanly against a namespace with no agent pods.

Assisted-By: Claude (Anthropic AI) noreply@anthropic.com

@oblinder
oblinder marked this pull request as ready for review August 4, 2026 12:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with 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.

Inline comments:
In `@aiac/demo/use-cases/uc1-onboarding/onboard/04-onboard-tool.py`:
- Line 46: Update the onboarding flow around ensure_default_audience_scope() to
detect when the audience scope assignment fails, using a returned success flag
or propagated exception. Stop onboarding before its success reporting path when
assignment is unsuccessful, while preserving normal continuation when the scope
is assigned.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0c4e755b-1608-4530-9a30-5ffda23020b2

📥 Commits

Reviewing files that changed from the base of the PR and between 1b17ad5 and 88603a1.

📒 Files selected for processing (12)
  • aiac/CLAUDE.md
  • aiac/demo/assets/agents/github_agent/README.md
  • aiac/demo/assets/install.sh
  • aiac/demo/use-cases/uc1-onboarding/init/00-prereqs.py
  • aiac/demo/use-cases/uc1-onboarding/lib/scenario.py
  • aiac/demo/use-cases/uc1-onboarding/onboard/04-onboard-tool.py
  • aiac/demo/use-cases/uc1-onboarding/show-state.py
  • aiac/docs/agents/domain.md
  • aiac/docs/agents/issue-tracker.md
  • aiac/docs/specs/demo/github-agent.md
  • scripts/opa-kind-enable.sh
  • scripts/opa-kind-restore.sh
🚧 Files skipped from review as they are similar to previous changes (8)
  • aiac/docs/agents/domain.md
  • aiac/demo/assets/install.sh
  • aiac/demo/use-cases/uc1-onboarding/lib/scenario.py
  • aiac/docs/agents/issue-tracker.md
  • aiac/demo/use-cases/uc1-onboarding/show-state.py
  • scripts/opa-kind-restore.sh
  • aiac/demo/use-cases/uc1-onboarding/init/00-prereqs.py
  • aiac/CLAUDE.md

Comment thread aiac/demo/use-cases/uc1-onboarding/onboard/04-onboard-tool.py Outdated
CodeRabbit flagged that 04-onboard-tool.py reported success even when
ensure_default_audience_scope() silently skipped assignment. After tool
onboarding the tool's *-aud client scope must exist; a missing scope
means the agent's exchanged tokens would lack the tool audience and
downstream calls would fail — yet onboarding still printed success.

- ensure_default_audience_scope() now returns bool: False when it skips
  because the scope is absent, True when assigned/already present.
- 04-onboard-tool.py aborts (non-zero) before the success message when
  the scope is missing post-onboarding.
- The 02-setup.py path (run() before the tool exists) still ignores the
  result, where a missing scope is expected and non-fatal.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Oleg Blinder <oblinder@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: New/ToDo

Development

Successfully merging this pull request may close these issues.

4 participants