fix(quality): stop CodeChangeAgent from overwriting Dockerfiles/.env.example; gate migration on a real database - #216
Merged
Conversation
…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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Root-cause fix for the containerfile/companion generation gap flagged in the fleet-wide PR quality audit, followed by a full removal of
CodeChangeAgentonce 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 legacyCodeChangeAgent(agents/codechange.py), which ran alongside the skill engine for high-criticality/low-score apps and had its own unguardeddockerfile/container/secretshandlers.Confirmed live, on PRs freshly regenerated after the
#213/#214/#215coverage-first quality pass had already shipped:RUN pip install --no-cache-dir .andCMD ["pulse-agent-api"], changedCOPY/WORKDIR, and left:latestunpinned on the very FROM line the finding was about.nginx->nodejs), still ended in:latest, droppedENTRYPOINT, and overwrote a real.env.examplewith a fabricated generic one -- violating thesecretscategory's ownauto_pr=Falsedetect-only contract.schema_migrations_baselinetable for a database-less React UI repo.Changes:
dockerfile/container/secretsfromCodeChangeAgent's dispatch (superseded by Part 2 below -- the whole agent is now gone).is_destructive_dockerfile_rewrite()as defense in depth: trackCMD/ENTRYPOINT/EXPOSEtoken drops, and a base-image-family-swap guard.migrationfinding/skill on a real database dependency (analyzers/data_governance.py::has_database_dependency(), reusingstack_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/WORKDIRpreserved and no base-image swap; no.env.examplegenerated; no migration finding/files for the database-less pulse-ui.Part 2: Remove CodeChangeAgent entirely
Follow-up audit: with
dockerfile/container/secretsremoved, checked what was left and found nothing worth keeping.healthworkload-health-probesskill (K8stcpSocketprobes, no app code needed). CodeChangeAgent instead drops an unwiredhealthz.py/.js/.gostub -- the same "orphan stub" shape already fixed once foraudit.py(pinky#8/#12).logging/structuredauto_pr=FalseinSOLUTION_CONTRACTS("human wires logging") -- violated it, same bug class assecretsabove.instrumentation/otel/opentelemetry/tracingauto_pr=Falseby contract; no skill/analyzer currently emits these categories.gitignoreDeleted
agents/codechange.py, itsagents/codechange.mdregistration, andtests/test_codechange_agent.py.AGENT_CLASSESis 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 fakeAgentviaAGENT_CLASSES/agent_filterto 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 fordelivery: sourceskill 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.