Skip to content

fix(runner,backend,control-plane): workflow dir creation + jira-write flag propagation#1702

Open
jwm4 wants to merge 1 commit into
mainfrom
fix/workflow-dir-jira-write-flags
Open

fix(runner,backend,control-plane): workflow dir creation + jira-write flag propagation#1702
jwm4 wants to merge 1 commit into
mainfrom
fix/workflow-dir-jira-write-flags

Conversation

@jwm4

@jwm4 jwm4 commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Three related bugs fixed across the runner, backend, and ambient-control-plane.


Bug 1 — Custom workflow fails to load when /workspace/workflows doesn't exist (fixes #1493)

Root cause — workflow.py

clone_workflow_at_runtime called shutil.move(temp_dir, workflow_final) without first creating workflow_final.parent (/workspace/workflows). If no prior workflow had been cloned in this session the directory did not exist, causing a FileNotFoundError silently swallowed by the outer try/except. The workflow appeared to load but nothing was placed in the session context.

The same missing mkdir was present in the subpath-not-found fallback path.

Fix: add workflow_final.parent.mkdir(parents=True, exist_ok=True) before both shutil.move callsites.

Root cause — hydrate.sh (init-container)

Identical gap in the shell script. The subpath-found path already ran mkdir -p "$(dirname "$WORKFLOW_FINAL")" but the no-subpath path and the subpath-not-found fallback both called mv without creating the parent first, silently failing under set +e.

Fix: add mkdir -p before mv in both shell paths.


Bug 2 — Intermittent jira-write failure in backend/operator path (fixes #1506)

Root cause

getWorkspaceOverrides was called with reqK8s (the user's bearer token) rather than K8sClient (the backend service account). End-users may not have configmaps.get permission in the project namespace, causing the lookup to fail intermittently. When it failed the code fell back to the global Unleash flag; if jira-write was only workspace-enabled (not globally enabled in Unleash) the fallback returned false and JIRA_READ_ONLY_MODE was never set to "false", so mcp-atlassian started in read-only mode and write tools (jira_add_comment, etc.) were absent.

Fix: switch to K8sClient (backend SA) for the ConfigMap read — the SA has the necessary permissions and this is a server-side concern.


Bug 3 — JIRA_READ_ONLY_MODE never reached Jira credential sidecar in control-plane path (fixes #1506)

Root cause

In the ambient-control-plane deployment, Jira credentials are served by a dedicated sidecar container that runs uvx mcp-atlassian. JIRA_READ_ONLY_MODE was injected into the runner container's env but buildCredentialSidecars built each sidecar's env independently and never received this variable. As a result mcp-atlassian always started in read-only mode regardless of the jira-write flag.

Fix:

  • Add parseSessionEnvVars() to deserialise the JSON-encoded EnvironmentVariables field from the Session SDK type.
  • Thread sessionEnvVars through to buildCredentialSidecars.
  • Inject JIRA_READ_ONLY_MODE into the Jira sidecar env when present — and only for the Jira sidecar (GitHub/K8s/Google sidecars are unaffected).

Files changed

File Change
components/runners/ambient-runner/ambient_runner/endpoints/workflow.py Add mkdir before shutil.move (2 callsites)
components/runners/state-sync/hydrate.sh Add mkdir -p before mv (2 callsites)
components/backend/handlers/sessions.go Use K8sClient instead of reqK8s for ConfigMap read
components/ambient-control-plane/internal/reconciler/kube_reconciler.go Add parseSessionEnvVars, propagate JIRA_READ_ONLY_MODE to Jira sidecar
components/ambient-control-plane/internal/reconciler/kube_reconciler_test.go Update existing tests; add 7 new tests
components/runners/ambient-runner/tests/test_workflow_endpoint.py New: 5 tests for clone_workflow_at_runtime

Testing

  • Go tests: cd components/ambient-control-plane && go test ./internal/reconciler/...
  • Python tests: cd components/runners/ambient-runner && python -m pytest tests/test_workflow_endpoint.py -v

Summary by CodeRabbit

  • New Features

    • Added support for Jira read-only mode configuration in credential handling.
  • Bug Fixes

    • Resolved workflow cloning failures when parent directories don't exist.
    • Improved reliability of feature flag override lookups by eliminating permission-related intermittent failures.
  • Tests

    • Added comprehensive test coverage for Jira read-only mode propagation.
    • Added unit tests for workflow endpoint cloning behavior and edge cases.

…flag propagation

Three bugs fixed across runner, backend, and ambient-control-plane.

## fix(runner): create /workspace/workflows before shutil.move (#1493)

clone_workflow_at_runtime in workflow.py called shutil.move(temp_dir,
workflow_final) without first creating workflow_final.parent
(/workspace/workflows).  If no prior workflow had been cloned the
directory did not exist, causing a FileNotFoundError that was silently
swallowed by the outer try/except.  The workflow then appeared to load
but nothing was placed in the session context.

The same missing mkdir existed for the subpath-not-found fallback path.

Fixes both paths:
- no-subpath (primary): add workflow_final.parent.mkdir(parents=True, exist_ok=True)
- subpath-not-found fallback: same fix

Added tests in tests/test_workflow_endpoint.py covering:
- no-subpath path creates parent directory when absent
- no-subpath path works when parent already exists
- subpath-not-found fallback creates parent directory
- git clone failure returns (False, '') without raising

## fix(runner): create /workspace/workflows in hydrate.sh (#1493)

Identical gap in the init-container shell script.  The subpath case
already ran mkdir -p "$(dirname "$WORKFLOW_FINAL")" but the
no-subpath path and the subpath-not-found fallback path both called mv
without creating the parent first, silently failing under set +e.

Added mkdir -p before mv in both shell paths.

## fix(backend): use service-account client for workspace feature-flag ConfigMap (#1506)

getWorkspaceOverrides was called with reqK8s (the user's bearer token)
rather than K8sClient (the backend service account).  End-users may not
have configmaps.get permission in the project namespace, causing the
RBAC check to fail intermittently.  When it failed the code fell back to
the global Unleash flag; if jira-write was only workspace-enabled (not
globally enabled) the fallback returned false and JIRA_READ_ONLY_MODE
was never set to "false", so mcp-atlassian started in read-only mode
and write tools (jira_add_comment, etc.) were not registered.

Switch to K8sClient so the ConfigMap read always uses a principal that
has the necessary permissions.

## fix(control-plane): propagate JIRA_READ_ONLY_MODE to Jira credential sidecar (#1506)

In the ambient-control-plane path, credential sidecars are separate
containers that each run the MCP server binary (e.g. uvx mcp-atlassian).
JIRA_READ_ONLY_MODE was set on the runner container env but the Jira
sidecar container env was built independently and never received this
variable, so mcp-atlassian always started in read-only mode regardless
of the jira-write flag.

Changes:
- Add parseSessionEnvVars() helper to deserialise the JSON-encoded
  EnvironmentVariables field from the Session SDK type.
- Pass sessionEnvVars to buildCredentialSidecars().
- In buildCredentialSidecars, inject JIRA_READ_ONLY_MODE into the Jira
  sidecar env when present in the session env — and only for the Jira
  sidecar (GitHub/K8s/Google sidecars are unaffected).
- Update all existing test callsites to pass the new parameter.
- Add new tests: JIRA_READ_ONLY_MODE set to false, not set by default,
  not propagated to GitHub sidecar, and parseSessionEnvVars unit tests.
@netlify

netlify Bot commented Jun 20, 2026

Copy link
Copy Markdown

Deploy Preview for cheerful-kitten-f556a0 canceled.

Name Link
🔨 Latest commit 35befde
🔍 Latest deploy log https://app.netlify.com/projects/cheerful-kitten-f556a0/deploys/6a36f970ab25aa0008829c6b

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Three independent bug fixes: the Go reconciler now parses session environment variables and propagates JIRA_READ_ONLY_MODE into the Jira credential sidecar; CreateSession switches from the user-scoped to service-account K8s client for workspace override lookups; and both the Python ambient runner and hydrate shell script now create missing parent directories before moving cloned workflow directories.

Changes

Jira JIRA_READ_ONLY_MODE Propagation to Credential Sidecar

Layer / File(s) Summary
Session env parsing and sidecar wiring
components/ambient-control-plane/internal/reconciler/kube_reconciler.go
parseSessionEnvVars deserializes JSON session env vars into a map[string]string. ensurePod calls it and threads the result into buildCredentialSidecars, which conditionally appends JIRA_READ_ONLY_MODE to the Jira sidecar env list.
Sidecar and parseSessionEnvVars tests
components/ambient-control-plane/internal/reconciler/kube_reconciler_test.go
Existing tests updated to pass noSessionEnv. New tests assert JIRA_READ_ONLY_MODE is "false" in write mode, absent by default, never injected into GitHub sidecars, and parseSessionEnvVars handles valid JSON, empty, invalid, and {} input.

Backend K8s Client Fix for Workspace Overrides

Layer / File(s) Summary
Service-account K8s client for workspace override lookup
components/backend/handlers/sessions.go
CreateSession calls getWorkspaceOverrides with K8sClient (service-account) instead of reqK8s (user-scoped), preventing RBAC failures when the user token lacks configmaps.get permission.

Workflow Clone Parent Directory Creation Fix

Layer / File(s) Summary
mkdir before workflow move
components/runners/ambient-runner/ambient_runner/endpoints/workflow.py, components/runners/state-sync/hydrate.sh
Both the Python runner and the hydrate shell script now create the destination parent directory before shutil.move/mv in both the subpath-missing and no-subpath branches.
clone_workflow_at_runtime tests
components/runners/ambient-runner/tests/test_workflow_endpoint.py
New pytest suite with five async tests covering: missing parent created on no-subpath clone, existing parent succeeds, git clone failure returns (False, ""), subpath-not-found falls back to whole repo and creates parent, and subpath-found extracts correct contents.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
Security And Secret Handling ❌ Error Authorization bypass: Control-plane directly propagates user-supplied JIRA_READ_ONLY_MODE from session env to sidecar without validating workspace authorization, allowing users to enable Jira write... Strip or filter JIRA_READ_ONLY_MODE from user-provided req.EnvironmentVariables in backend; set exclusively from workspace override server-side, not from untrusted session env in control-plane.
Docstring Coverage ⚠️ Warning Docstring coverage is 34.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Kubernetes Resource Safety ⚠️ Warning Child resources (Secrets, ServiceAccounts, RoleBindings, NetworkPolicies) created in kube_reconciler.go lack ownerReferences for cleanup automation. Additionally, Pod spec lacks pod-level securityC... Add metadata.ownerReferences to all child resources referencing the Session CRD; add pod-level securityContext with fsGroup and runAsUser to Pod spec for better isolation.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title follows Conventional Commits format (fix scope: description) and accurately summarizes all three bug fixes in the changeset.
Linked Issues check ✅ Passed All coding requirements from #1493 (workflow directory creation) and #1506 (backend token permissions and sidecar env var propagation) are fully addressed with proper implementations and test coverage.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the three identified bugs; no extraneous modifications or feature creep detected across the six modified files.
Performance And Algorithmic Complexity ✅ Passed All changes follow expected algorithmic patterns with no performance regressions: parseSessionEnvVars called once per session (O(n) baseline), buildCredentialSidecars iterates bounded credential pr...
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/workflow-dir-jira-write-flags
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/workflow-dir-jira-write-flags

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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
`@components/ambient-control-plane/internal/reconciler/kube_reconciler_test.go`:
- Around line 201-334: The test suite lacks a regression test for a critical
security boundary: when jira-write is disabled at the workspace level, a user
should not be able to bypass this by providing JIRA_READ_ONLY_MODE=false in the
sessionEnv. Add a new test function following the pattern of
TestBuildCredentialSidecars_JiraReadOnlyMode_SetToFalse that creates a
SimpleKubeReconciler without jira-write capability enabled, passes a sessionEnv
containing JIRA_READ_ONLY_MODE=false as untrusted user input, calls
buildCredentialSidecars, and asserts that JIRA_READ_ONLY_MODE is not injected
into the Jira sidecar (or remains in the safe read-only state), thereby
validating that workspace-level authorization controls cannot be overridden by
user-provided environment variables.

In `@components/ambient-control-plane/internal/reconciler/kube_reconciler.go`:
- Around line 1205-1207: The parseSessionEnvVars function silently returns an
empty map when JSON unmarshaling fails, which suppresses error details and makes
debugging difficult. Modify parseSessionEnvVars to return both the output map
and an error value. Update the unmarshal error handling to return the error
instead of silently returning an empty map. Then in the ensurePod method (which
calls parseSessionEnvVars), capture the returned error and add contextual
logging to make the failure explicit, ensuring errors are never silently
discarded. Apply this same fix to any other similar error handling patterns in
the file where environment variable parsing might fail.
- Around line 1265-1268: The code at the jira provider check block forwards
JIRA_READ_ONLY_MODE directly from sessionEnvVars which contains untrusted
user-provided environment variables. This allows users to bypass authorization
by setting JIRA_READ_ONLY_MODE=false to enable Jira write tools without
workspace permission. Remove the direct forwarding of JIRA_READ_ONLY_MODE from
sessionEnvVars in the if provider == "jira" block and instead set it exclusively
based on trusted server-side configuration such as workspace settings or
authorization checks. Only propagate JIRA_READ_ONLY_MODE to the environment if
it comes from an authorized server-controlled source, not from user input.

In `@components/runners/ambient-runner/tests/test_workflow_endpoint.py`:
- Line 98: In the async tests, the variable `path` is unpacked from the return
value of `clone_workflow_at_runtime()` at three locations (lines 98, 152, and
193) but is never used thereafter. Rename the unused `path` variable to `_path`
(or `_`) in all three occurrences where `clone_workflow_at_runtime()` is called
to follow Python conventions for unused variables and satisfy Ruff linting
requirements.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a0a6896d-7a62-488f-9ad5-20e52b3dba9c

📥 Commits

Reviewing files that changed from the base of the PR and between 3215c8a and 35befde.

📒 Files selected for processing (6)
  • components/ambient-control-plane/internal/reconciler/kube_reconciler.go
  • components/ambient-control-plane/internal/reconciler/kube_reconciler_test.go
  • components/backend/handlers/sessions.go
  • components/runners/ambient-runner/ambient_runner/endpoints/workflow.py
  • components/runners/ambient-runner/tests/test_workflow_endpoint.py
  • components/runners/state-sync/hydrate.sh

Comment on lines +201 to +334
func TestBuildCredentialSidecars_JiraReadOnlyMode_SetToFalse(t *testing.T) {
// When jira-write is enabled, JIRA_READ_ONLY_MODE=false must be passed to
// the Jira sidecar so mcp-atlassian exposes write tools.
r := &SimpleKubeReconciler{
cfg: KubeReconcilerConfig{
JiraMCPImage: "jira-mcp:latest",
MCPAPIServerURL: "http://api.svc:8000",
CPTokenURL: "http://cp.svc:8080",
CPTokenPublicKey: "test-key",
},
}
r.logger = r.logger.With().Logger()

credentialIDs := map[string]string{"jira": "cred-42"}
sessionEnv := map[string]string{"JIRA_READ_ONLY_MODE": "false"}

sidecars, _, _ := r.buildCredentialSidecars("test-session", "test-ns", credentialIDs, sessionEnv)

if len(sidecars) != 1 {
t.Fatalf("expected 1 sidecar, got %d", len(sidecars))
}

sidecar := sidecars[0].(map[string]interface{})
env := sidecar["env"].([]interface{})

val, found := findEnvVar(env, "JIRA_READ_ONLY_MODE")
if !found {
t.Fatal("JIRA_READ_ONLY_MODE not found in Jira sidecar env")
}
if val != "false" {
t.Errorf("expected JIRA_READ_ONLY_MODE=false, got %q", val)
}
}

func TestBuildCredentialSidecars_JiraReadOnlyMode_NotSetByDefault(t *testing.T) {
// When jira-write is not enabled, JIRA_READ_ONLY_MODE must NOT be injected
// (mcp-atlassian defaults to read-only, which is the safe default).
r := &SimpleKubeReconciler{
cfg: KubeReconcilerConfig{
JiraMCPImage: "jira-mcp:latest",
MCPAPIServerURL: "http://api.svc:8000",
CPTokenURL: "http://cp.svc:8080",
CPTokenPublicKey: "test-key",
},
}
r.logger = r.logger.With().Logger()

credentialIDs := map[string]string{"jira": "cred-42"}

sidecars, _, _ := r.buildCredentialSidecars("test-session", "test-ns", credentialIDs, noSessionEnv)

if len(sidecars) != 1 {
t.Fatalf("expected 1 sidecar, got %d", len(sidecars))
}

sidecar := sidecars[0].(map[string]interface{})
env := sidecar["env"].([]interface{})

_, found := findEnvVar(env, "JIRA_READ_ONLY_MODE")
if found {
t.Error("JIRA_READ_ONLY_MODE should not be present when jira-write is disabled")
}
}

func TestBuildCredentialSidecars_JiraReadOnly_NotPropagatedToGitHub(t *testing.T) {
// JIRA_READ_ONLY_MODE must only be injected into the Jira sidecar, never
// into other provider sidecars (e.g. GitHub).
r := &SimpleKubeReconciler{
cfg: KubeReconcilerConfig{
GitHubMCPImage: "github-mcp:latest",
JiraMCPImage: "jira-mcp:latest",
MCPAPIServerURL: "http://api.svc:8000",
CPTokenURL: "http://cp.svc:8080",
CPTokenPublicKey: "test-key",
},
}
r.logger = r.logger.With().Logger()

credentialIDs := map[string]string{"github": "cred-1", "jira": "cred-2"}
sessionEnv := map[string]string{"JIRA_READ_ONLY_MODE": "false"}

sidecars, _, _ := r.buildCredentialSidecars("test-session", "test-ns", credentialIDs, sessionEnv)

if len(sidecars) != 2 {
t.Fatalf("expected 2 sidecars, got %d", len(sidecars))
}

for _, item := range sidecars {
sidecar := item.(map[string]interface{})
name := sidecar["name"].(string)
if name == "credential-github" {
env := sidecar["env"].([]interface{})
if _, found := findEnvVar(env, "JIRA_READ_ONLY_MODE"); found {
t.Errorf("JIRA_READ_ONLY_MODE must not be injected into GitHub sidecar")
}
}
}
}

// ---------------------------------------------------------------------------
// Tests for parseSessionEnvVars
// ---------------------------------------------------------------------------

func TestParseSessionEnvVars_ValidJSON(t *testing.T) {
raw := `{"JIRA_READ_ONLY_MODE":"false","FOO":"bar"}`
got := parseSessionEnvVars(raw)
if got["JIRA_READ_ONLY_MODE"] != "false" {
t.Errorf("expected false, got %q", got["JIRA_READ_ONLY_MODE"])
}
if got["FOO"] != "bar" {
t.Errorf("expected bar, got %q", got["FOO"])
}
}

func TestParseSessionEnvVars_Empty(t *testing.T) {
got := parseSessionEnvVars("")
if len(got) != 0 {
t.Errorf("expected empty map, got %v", got)
}
}

func TestParseSessionEnvVars_InvalidJSON(t *testing.T) {
got := parseSessionEnvVars("not-json")
if len(got) != 0 {
t.Errorf("expected empty map on parse error, got %v", got)
}
}

func TestParseSessionEnvVars_EmptyObject(t *testing.T) {
got := parseSessionEnvVars("{}")
if len(got) != 0 {
t.Errorf("expected empty map, got %v", got)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing regression test for unauthorized Jira write-mode enablement

Current tests validate positive propagation behavior, but they don’t protect against the critical case where JIRA_READ_ONLY_MODE=false comes from user-provided session env while workspace jira-write is disabled. Add a negative test that asserts write mode is not enabled from untrusted env input.

🤖 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 `@components/ambient-control-plane/internal/reconciler/kube_reconciler_test.go`
around lines 201 - 334, The test suite lacks a regression test for a critical
security boundary: when jira-write is disabled at the workspace level, a user
should not be able to bypass this by providing JIRA_READ_ONLY_MODE=false in the
sessionEnv. Add a new test function following the pattern of
TestBuildCredentialSidecars_JiraReadOnlyMode_SetToFalse that creates a
SimpleKubeReconciler without jira-write capability enabled, passes a sessionEnv
containing JIRA_READ_ONLY_MODE=false as untrusted user input, calls
buildCredentialSidecars, and asserts that JIRA_READ_ONLY_MODE is not injected
into the Jira sidecar (or remains in the safe read-only state), thereby
validating that workspace-level authorization controls cannot be overridden by
user-provided environment variables.

Comment on lines +1205 to +1207
if err := json.Unmarshal([]byte(raw), &out); err != nil {
return map[string]string{}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t silently drop malformed session env JSON

parseSessionEnvVars returns {} on unmarshal failure without logging or error propagation. That silently disables sidecar env propagation (including JIRA_READ_ONLY_MODE) and makes failures hard to diagnose.

Please return an error from parseSessionEnvVars and handle it in ensurePod with contextual logging (without logging env values), so the failure path is explicit.

As per coding guidelines, "Never silently swallow partial failures; every error path must propagate or be explicitly collected, never discarded".

Also applies to: 587-588

🤖 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 `@components/ambient-control-plane/internal/reconciler/kube_reconciler.go`
around lines 1205 - 1207, The parseSessionEnvVars function silently returns an
empty map when JSON unmarshaling fails, which suppresses error details and makes
debugging difficult. Modify parseSessionEnvVars to return both the output map
and an error value. Update the unmarshal error handling to return the error
instead of silently returning an empty map. Then in the ensurePod method (which
calls parseSessionEnvVars), capture the returned error and add contextual
logging to make the failure explicit, ensuring errors are never silently
discarded. Apply this same fix to any other similar error handling patterns in
the file where environment variable parsing might fail.

Source: Coding guidelines

Comment on lines +1265 to +1268
if provider == "jira" {
if v, ok := sessionEnvVars["JIRA_READ_ONLY_MODE"]; ok {
env = append(env, envVar("JIRA_READ_ONLY_MODE", v))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Untrusted session env can bypass Jira write-mode authorization

JIRA_READ_ONLY_MODE is forwarded directly from sessionEnvVars, but upstream session env includes user-provided req.EnvironmentVariables. A user can set JIRA_READ_ONLY_MODE=false even when jira-write is disabled, which enables Jira write tools in the sidecar without workspace authorization.

This key must be server-controlled only (e.g., strip/ignore user-provided JIRA_READ_ONLY_MODE in backend and set it exclusively from workspace override, or gate propagation on a trusted server-side marker).

🤖 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 `@components/ambient-control-plane/internal/reconciler/kube_reconciler.go`
around lines 1265 - 1268, The code at the jira provider check block forwards
JIRA_READ_ONLY_MODE directly from sessionEnvVars which contains untrusted
user-provided environment variables. This allows users to bypass authorization
by setting JIRA_READ_ONLY_MODE=false to enable Jira write tools without
workspace permission. Remove the direct forwarding of JIRA_READ_ONLY_MODE from
sessionEnvVars in the if provider == "jira" block and instead set it exclusively
based on trusted server-side configuration such as workspace settings or
authorization checks. Only propagate JIRA_READ_ONLY_MODE to the environment if
it comes from an authorized server-controlled source, not from user input.

), patch(
"os.getenv", side_effect=lambda k, d="": str(workspace) if k == "WORKSPACE_PATH" else d
):
success, path = await clone_workflow_at_runtime(git_url, "main", "")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove unused path bindings in async tests.

At Line 98, Line 152, and Line 193, path is unpacked but unused. Rename to _path (or _) to keep Ruff clean and avoid CI noise.

Suggested patch
-        success, path = await clone_workflow_at_runtime(git_url, "main", "")
+        success, _path = await clone_workflow_at_runtime(git_url, "main", "")
@@
-        success, path = await clone_workflow_at_runtime(
+        success, _path = await clone_workflow_at_runtime(
             git_url, "main", "does-not-exist"
         )
@@
-        success, path = await clone_workflow_at_runtime(
+        success, _path = await clone_workflow_at_runtime(
             git_url, "main", "workflows/my-workflow"
         )

Also applies to: 152-152, 193-193

🧰 Tools
🪛 Ruff (0.15.17)

[warning] 98-98: Unpacked variable path is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

🤖 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 `@components/runners/ambient-runner/tests/test_workflow_endpoint.py` at line
98, In the async tests, the variable `path` is unpacked from the return value of
`clone_workflow_at_runtime()` at three locations (lines 98, 152, and 193) but is
never used thereafter. Rename the unused `path` variable to `_path` (or `_`) in
all three occurrences where `clone_workflow_at_runtime()` is called to follow
Python conventions for unused variables and satisfy Ruff linting requirements.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Intermittent failure to register jira-write MCP tools in active sessions Bug: Problems loading custom workflow

1 participant