Skip to content

fix(quality): stop CodeChangeAgent from overwriting Dockerfiles/.env.example; gate migration on a real database - #216

Merged
alimobrem merged 3 commits into
mainfrom
fix/containerfile-harden-additive
Aug 7, 2026
Merged

fix(quality): stop CodeChangeAgent from overwriting Dockerfiles/.env.example; gate migration on a real database#216
alimobrem merged 3 commits into
mainfrom
fix/containerfile-harden-additive

Conversation

@alimobrem

@alimobrem alimobrem commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary

Root-cause fix for the containerfile/companion generation gap flagged in the fleet-wide PR quality audit, followed by a full removal of CodeChangeAgent once the audit showed nothing worth keeping in it.

Part 1: Fix the root cause (not the skill-engine path -- that's correct)

The bug was not in the skill-engine's own pin/harden path (source_patches.py) -- that's verified correct -- it was the legacy CodeChangeAgent (agents/codechange.py), which ran alongside the skill engine for high-criticality/low-score apps and had its own unguarded dockerfile/container/secrets handlers.

Confirmed live, on PRs freshly regenerated after the #213/#214/#215 coverage-first quality pass had already shipped:

  • PulseSRE/pulse-agent#2: deleted RUN pip install --no-cache-dir . and CMD ["pulse-agent-api"], changed COPY/WORKDIR, and left :latest unpinned on the very FROM line the finding was about.
  • PulseSRE/pulse-ui#39: swapped the base image family (nginx -> nodejs), still ended in :latest, dropped ENTRYPOINT, and overwrote a real .env.example with a fabricated generic one -- violating the secrets category's own auto_pr=False detect-only contract.
  • PulseSRE/pulse-ui#40: invented a schema_migrations_baseline table for a database-less React UI repo.

Changes:

  • Removed dockerfile/container/secrets from CodeChangeAgent's dispatch (superseded by Part 2 below -- the whole agent is now gone).
  • Hardened is_destructive_dockerfile_rewrite() as defense in depth: track CMD/ENTRYPOINT/EXPOSE token drops, and a base-image-family-swap guard.
  • Gated the migration finding/skill on a real database dependency (analyzers/data_governance.py::has_database_dependency(), reusing stack_detector.DB_PATTERNS/DEP_FILES).

Verified end-to-end against the real, current pulse-agent/pulse-ui repo content through the actual delivery-time enrichment functions -- all Dockerfiles now pin correctly with RUN/CMD/ENTRYPOINT/COPY/WORKDIR preserved and no base-image swap; no .env.example generated; no migration finding/files for the database-less pulse-ui.

Part 2: Remove CodeChangeAgent entirely

Follow-up audit: with dockerfile/container/secrets removed, checked what was left and found nothing worth keeping.

Category Status Problem
health Live Duplicates the already-guarded workload-health-probes skill (K8s tcpSocket probes, no app code needed). CodeChangeAgent instead drops an unwired healthz.py/.js/.go stub -- the same "orphan stub" shape already fixed once for audit.py (pinky#8/#12).
logging/structured Live Contractually auto_pr=False in SOLUTION_CONTRACTS ("human wires logging") -- violated it, same bug class as secrets above.
instrumentation/otel/opentelemetry/tracing Dead code Also auto_pr=False by contract; no skill/analyzer currently emits these categories.
gitignore Dead code No analyzer ever emits this category.

Deleted agents/codechange.py, its agents/codechange.md registration, and tests/test_codechange_agent.py. AGENT_CLASSES is now always empty -- no Python onboarding agent remains at all; skills own every remediation domain. Updated orchestrator/capabilities/agent_registry_cleanup and portal wiring accordingly, and fixed every test that asserted the old "codechange gets planned for high-criticality" behavior -- several now inject a minimal fake Agent via AGENT_CLASSES/agent_filter to keep the real local/K8s-Job execution and crash-resilience code paths exercised now that no production Python agent exists to run them against.

The "codechange" string survives only as an internal category/branch-naming convention for delivery: source skill output (containerfile, db-migration-tooling, app-audit-logging, helm-chart, ...) -- decoupled from, and unrelated to, the deleted Python class of the same name.

Verification

uv run pytest: 3491 passed (full suite, twice, to rule out the transient Postgres-container flakiness this session has previously diagnosed).

Next steps (not done in this PR -- needs cluster access / deploy)

The 3 broken PRs above are still open. They should be closed and replaced by a fresh Scan once this merges and deploys -- I don't have live cluster access this session to trigger that Scan or verify the deployed commit.

Ali Mobrem and others added 3 commits August 6, 2026 08:31
…example; gate migration on a real database

Root cause of two live-confirmed, freshly-regenerated bugs (pulse-agent#2,
pulse-ui#39/#40) traced past the skill-engine's own pin/harden path (which
is correct) to the legacy CodeChangeAgent (agents/codechange.py): its
dockerfile/container/secrets handlers did a blind action="create" full-file
overwrite with no knowledge of the existing file, and carried no skill_name
the enrichment pipeline (apply_containerfile_pin_only/harden_dockerfile_content/
is_destructive_dockerfile_rewrite) recognizes -- so they bypassed every
guardrail the newer path has.

- Remove dockerfile/container/secrets from CodeChangeAgent's dispatch
  entirely. containerfile skill's pin/harden path now exclusively owns
  Dockerfile fixes; secrets stays contractually detect-only (SOLUTION_CONTRACTS
  auto_pr=False) instead of auto-PR'ing a fabricated .env.example.
- Harden is_destructive_dockerfile_rewrite() as defense in depth: track
  CMD/ENTRYPOINT/EXPOSE token drops (previously only RUN/COPY/ADD/ARG/ENV/
  WORKDIR), and add a base-image-family-swap guard (e.g. nginx -> nodejs).
- Gate the "migration" finding/skill on has_database_dependency() (reusing
  stack_detector.DB_PATTERNS) -- a database-less app has nothing to migrate;
  confirmed live pulse-ui#40 invented a fake schema_migrations_baseline table
  for a React UI repo with no database.

Verified end-to-end against the real, current pulse-agent/pulse-ui repo
content: all three Dockerfiles now pin correctly with RUN/CMD/ENTRYPOINT
preserved and no base-image swap; no .env.example generated; no migration
finding/files for pulse-ui.

Co-authored-by: Cursor <cursoragent@cursor.com>
Follow-up audit after the containerfile/companion fix in this branch:
checked what was left in CodeChangeAgent's dispatch and found nothing
worth keeping.

- health: duplicates the already-guarded workload-health-probes skill,
  which clears the finding via K8s tcpSocket probes with no app-code
  stub required. CodeChangeAgent's version drops an unwired healthz.py/
  .js/.go file with a comment telling a human to wire it in manually --
  the same "orphan stub that never actually clears anything" shape
  already fixed once for audit.py (pinky#8/#12).
- logging/structured: live (fires via the structured-logging-detected
  detect skill), but logging is contractually auto_pr=False in
  SOLUTION_CONTRACTS ("human wires logging") -- CodeChangeAgent violated
  this exact contract, same class of bug as the secrets/.env.example fix
  earlier in this branch.
- instrumentation/otel/opentelemetry/tracing: also auto_pr=False by
  contract, and dead code today (no skill/analyzer currently emits these
  categories).
- gitignore: dead code -- no analyzer ever emits this category.

Deleted agents/codechange.py, its agents/codechange.md registration, and
tests/test_codechange_agent.py. AGENT_CLASSES is now always empty -- no
Python onboarding agent remains at all; skills own every remediation
domain. Updated orchestrator.py/capabilities.py/agent_registry_cleanup.py
and portal wiring (delivery.py/helpers.py/github_pr.py/routes/
capabilities.py) accordingly, and fixed every test that asserted the
old codechange-gets-planned-for-high-criticality behavior -- several now
inject a minimal fake Agent via AGENT_CLASSES/agent_filter to keep the
real local/K8s-Job execution and crash-resilience code paths exercised
now that no production Python agent exists to run them against.

The "codechange" string survives only as an internal category/branch-
naming convention for delivery: source skill output (containerfile,
db-migration-tooling, app-audit-logging, helm-chart, ...) -- decoupled
from, and unrelated to, the deleted Python class of the same name.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ast HIGH CVEs

Two independent CI failures on this PR, both fixed here:

1. image-smoke-test: removing agents/codechange.py's registration file
   (agents/codechange.md) in the prior commit left repo-root agents/ with
   zero tracked files. Git doesn't track empty directories, so agents/
   doesn't exist at all in a fresh checkout, and the Containerfile's
   `COPY agents/ agents/` failed outright ("/agents": not found) -- the
   same class of bug checks/ hit when its last file was ported to a
   skill. capabilities.py's load_agent_classes() already handles a
   missing agents_dir gracefully, so this was purely a build-context
   problem. Removed the now-pointless COPY line (with a comment
   explaining why, mirroring checks/'s existing one). Confirmed no chart
   template, .dockerignore, or CI workflow referenced the directory, and
   grepped the repo for any other stale codechange/CodeChangeAgent code
   reference -- none found; every remaining "codechange" string is an
   intentionally-kept internal category label. Verified by building the
   real Containerfile locally end-to-end (from a clean non-worktree
   clone) and running the CI job's exact smoke-test shell checks against
   the built image -- all passed.

2. Security Scan (Trivy): two new HIGH CVEs in transitive deps --
   CVE-2026-69244 (aiohttp 3.14.1, via kubernetes; fixed 3.14.3) and
   CVE-2026-69247 (cryptography 49.0.0, via google-auth/anthropic[vertex];
   fixed 50.0.0). Neither is a direct dependency, so added
   `[tool.uv] constraint-dependencies` to pyproject.toml instead of
   bumping a `dependencies` entry (the gitpython-fix pattern doesn't
   apply directly here since gitpython is a direct dep). Floors land on
   each package's actual latest stable release, not just the minimum
   fixed version, same discipline as the gitpython fix. uv lock resolved
   exactly those two bumps, nothing else. Verified locally: Trivy fs scan
   against the regenerated uv.lock -- 0 vulnerabilities, down from 2
   HIGH; full non-browser suite green (3496 passed, 441 skipped, 0
   failed, matching CI's test job's exact ignore list) plus all 6
   browser-critical Playwright journeys.

Co-authored-by: Cursor <cursoragent@cursor.com>
@alimobrem
alimobrem merged commit 130df97 into main Aug 7, 2026
4 checks passed
alimobrem pushed a commit that referenced this pull request Aug 11, 2026
…), not just past today's known CVEs

Security Scan (Trivy) on PR #217 (an unrelated skill-cooldown
manual-clear feature) failed again -- the "take 2" floor of >=3.1.57
held for barely five days before 5 new HIGH findings landed against
installed 3.1.57: GHSA-4gmw-gg2m-w46p, GHSA-9rj7-rf2p-w77r,
GHSA-hmq2-w58f-27jc, GHSA-jm78-9fvv-mhgr, GHSA-wvpp-8hx9-p66j (plus
GHSA-hh9p-6wh2-4mfc), all fixed in 3.1.58.

Bumping straight to 3.1.58 would repeat the exact "minimum version
that clears today's known CVEs" mistake that caused this to recur a
third time -- and indeed, GitHub's own release notes show 3.1.59
shipped the very next day (2026-08-10) with two more security
advisories (GHSA-5xxx-qhh7-9287, GHSA-3wxw-xv34-2frg), which would
have re-broken the scan within 24 hours of a 3.1.58 bump.

pyproject.toml's gitpython floor raised to >=3.1.59 (verified against
PyPI as the actual current latest stable release, same discipline as
takes 1/2 and the aiohttp/cryptography constraint-dependencies fix on
PR #216). uv lock resolved 3.1.59 cleanly, no other package changed.
Checked 3.1.58/3.1.59's new hardening (blocks separate git directories
during clone, blocks file-reading git options, hardens config-parsing
boundaries) against the only real call site in this codebase --
cloner.py's Repo.clone_from() via clone_repo(), a plain
depth/branch/env invocation using none of the newly-blocked options --
so no behavior change there.

Verified directly, not just by trusting the version bump: ran the same
Trivy fs scan locally against the regenerated uv.lock -- 0
vulnerabilities, down from 5 HIGH. tests/test_cloner.py (14 tests)
pass unchanged against 3.1.59. Full non-browser suite green: 3511
passed, 441 skipped, 0 failed (matching CI's test job's exact ignore
list).

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

1 participant