code-modernization: harden writes a patch instead of editing legacy; make map/security guidance language-agnostic

- modernize-harden: never edits legacy/ anymore. Writes findings plus a
  reviewed unified diff to analysis/<system>/security_remediation.patch.
  A second security-auditor pass reviews each hunk (RESOLVES / PARTIAL /
  INTRODUCES-RISK) before presenting. The user reviews and applies the
  patch deliberately, then re-runs to verify. This makes every command
  consistent with the recommended deny Edit(legacy/**) workspace setting,
  so the README's exception note is gone.
- modernize-map: restructure the parse-target list around three stack-
  agnostic principles (dispatcher targets are variables; code-storage
  joins live in config; entry points live in deployment descriptors), with
  COBOL/Java/web/CLI examples on equal footing rather than COBOL-dominant.
  Same protections against false dead-code findings, less stack-specific.
- security-auditor agent: rephrase coverage items in stack-neutral terms
  (record layouts/temp datasets, resource ACLs, deployment scripts/job
  definitions, batch input records) so the checklist reads naturally for
  COBOL, Java EE, .NET, and web targets alike.
- README: drop the harden exception note; describe the patch workflow.
This commit is contained in:
Morgan Lunt 2026-05-11 16:46:03 -07:00
parent 22a1b25977
commit 5e4a45001d
No known key found for this signature in database
4 changed files with 94 additions and 72 deletions

View File

@ -10,7 +10,7 @@ Legacy modernization fails most often not because the target technology is wrong
assess → map → extract-rules → brief → reimagine | transform → harden assess → map → extract-rules → brief → reimagine | transform → harden
``` ```
The discovery commands (`assess`, `map`, `extract-rules`) build artifacts under `analysis/<system>/`. The `brief` command synthesizes them into an approval gate. The build commands (`reimagine`, `transform`) write new code under `modernized/`. The `harden` command audits and patches the legacy system. Each step has a dedicated slash command, and specialist agents (legacy analyst, business rules extractor, architecture critic, security auditor, test engineer) are invoked from within those commands — or directly — to keep the work honest. The discovery commands (`assess`, `map`, `extract-rules`) build artifacts under `analysis/<system>/`. The `brief` command synthesizes them into an approval gate. The build commands (`reimagine`, `transform`) write new code under `modernized/`. The `harden` command audits the legacy system and produces a reviewable remediation patch. Each step has a dedicated slash command, and specialist agents (legacy analyst, business rules extractor, architecture critic, security auditor, test engineer) are invoked from within those commands — or directly — to keep the work honest.
## Expected layout ## Expected layout
@ -47,9 +47,7 @@ Greenfield rebuild from extracted intent rather than a structural port. Mines a
Surgical, single-module strangler-fig rewrite. Plans first (HITL gate), then writes characterization tests via `test-engineer`, then an idiomatic target implementation under `modernized/<system>/<module>/`, proves equivalence by running the tests, and produces `TRANSFORMATION_NOTES.md` mapping legacy → modern with deliberate deviations called out. Reviewed by `architecture-critic`. Surgical, single-module strangler-fig rewrite. Plans first (HITL gate), then writes characterization tests via `test-engineer`, then an idiomatic target implementation under `modernized/<system>/<module>/`, proves equivalence by running the tests, and produces `TRANSFORMATION_NOTES.md` mapping legacy → modern with deliberate deviations called out. Reviewed by `architecture-critic`.
### `/modernize-harden <system-dir>` ### `/modernize-harden <system-dir>`
Security hardening pass on the **legacy** system: OWASP/CWE scan, dependency CVEs, secrets, injection. Spawns `security-auditor`. Produces `analysis/<system>/SECURITY_FINDINGS.md` ranked Critical / High / Medium / Low, then **patches Critical and High findings directly in `legacy/<system>/`** and re-scans to verify. Useful as a pre-modernization step when the legacy system will keep running in production during the migration. Security hardening pass on the **legacy** system: OWASP/CWE scan, dependency CVEs, secrets, injection. Spawns `security-auditor`. Produces `analysis/<system>/SECURITY_FINDINGS.md` ranked Critical / High / Medium / Low and a reviewed `analysis/<system>/security_remediation.patch` with minimal fixes for the Critical/High findings. The patch is reviewed by a second `security-auditor` pass before you see it. **Never edits `legacy/`** — you review and apply the patch yourself when ready, then re-run to verify. Useful as a pre-modernization step when the legacy system will keep running in production during the migration.
> **Note:** `/modernize-harden` is the one command that edits `legacy/`. If you adopt the `deny: Edit(legacy/**)` workspace setting below, relax it for this command — or run hardening as a separate workstream against its own checkout.
## Agents ## Agents
@ -89,7 +87,7 @@ This plugin ships commands and agents, but modernization projects benefit from a
} }
``` ```
Adjust `legacy/` and `modernized/` to match your actual layout. The key invariants: `Edit` under `legacy/` is denied, and writes are scoped to `analysis/` (for documents) and `modernized/` (for the new code). The exception is `/modernize-harden`, which intentionally patches `legacy/` — see its note above. Adjust `legacy/` and `modernized/` to match your actual layout. The key invariants: `Edit` under `legacy/` is denied, and writes are scoped to `analysis/` (for documents) and `modernized/` (for the new code). Every command in this plugin respects this — `/modernize-harden` writes a patch to `analysis/` rather than editing `legacy/` in place.
## Typical Workflow ## Typical Workflow

View File

@ -11,28 +11,29 @@ engineer can fix.
## Coverage checklist ## Coverage checklist
Adapt to the target stack — web items don't apply to a batch COBOL system, Adapt to the target stack — web items don't apply to a batch system,
mainframe items don't apply to a SPA. Work through what's relevant: terminal/screen items don't apply to a SPA. Work through what's relevant:
- **Injection** (SQL, NoSQL, OS command, LDAP, XPath, template, dynamic - **Injection** (SQL, NoSQL, OS command, LDAP, XPath, template) — trace every
DB2 SQL, JCL/PARM injection) — trace every user-controlled input to every sink user-controlled input to every sink, including dynamic SQL and shell-outs
- **Authentication / session** — hardcoded creds, weak session handling, - **Authentication / session** — hardcoded creds, weak session handling,
missing auth checks on sensitive routes/transactions missing auth checks on sensitive routes/transactions/jobs
- **Sensitive data exposure** — secrets in source, weak crypto, PII/PAN/SSN in - **Sensitive data exposure** — secrets in source, weak crypto, PII in logs,
logs, cleartext data in copybooks/flat files cleartext sensitive data in record layouts, flat files, or temp datasets
- **Access control** — IDOR, missing ownership checks, privilege escalation; - **Access control** — IDOR, missing ownership checks, privilege escalation;
for CICS: missing/permissive RACF transaction & resource definitions, missing/permissive resource ACLs (RACF profiles, IAM policies, file perms);
unguarded admin transactions unguarded admin functions
- **XSS / CSRF** — unescaped output, missing tokens (web targets only) - **XSS / CSRF** — unescaped output, missing tokens (web targets)
- **Insecure deserialization** — pickle/yaml.load/ObjectInputStream on - **Insecure deserialization**untrusted data into pickle/yaml.load/
untrusted data `ObjectInputStream` or custom record parsers
- **Vulnerable dependencies** — run `npm audit` / `pip-audit` / - **Vulnerable dependencies** — run `npm audit` / `pip-audit` /
read manifests and flag versions with known CVEs read manifests and flag versions with known CVEs
- **SSRF / path traversal / open redirect** (web targets only) - **SSRF / path traversal / open redirect** (web/network targets)
- **Input validation** — for CICS/3270: unvalidated BMS field input, - **Input validation** — missing length/range/format checks at trust
missing length/range/format checks before file/DB writes boundaries (form/screen fields, API params, batch input records) before
persistence or downstream calls
- **Security misconfiguration** — debug mode, verbose errors, default creds, - **Security misconfiguration** — debug mode, verbose errors, default creds,
hardcoded passwords/userids in JCL, PROCs, or sign-on programs hardcoded credentials in deployment scripts, job definitions, or config
## Tooling ## Tooling

View File

@ -1,23 +1,26 @@
--- ---
description: Security vulnerability scan + remediation — OWASP, CVE, secrets, injection description: Security vulnerability scan with a reviewable remediation patch — OWASP, CWE, CVE, secrets, injection
argument-hint: <system-dir> argument-hint: <system-dir>
--- ---
Run a **security hardening pass** on `legacy/$1`: find vulnerabilities, rank Run a **security hardening pass** on `legacy/$1`: find vulnerabilities, rank
them, and fix the critical ones. them, and produce a reviewable patch for the critical ones.
This command never edits `legacy/` — it writes findings and a proposed patch
to `analysis/$1/`. The user reviews and applies (or not).
## Scan ## Scan
Spawn the **security-auditor** subagent: Spawn the **security-auditor** subagent:
"Adversarially audit legacy/$1 for security vulnerabilities. Cover: "Adversarially audit legacy/$1 for security vulnerabilities. Cover what's
OWASP Top 10 (injection, broken auth, XSS, SSRF, etc.), hardcoded secrets, relevant to the stack: injection (SQL/NoSQL/OS command/template), broken
vulnerable dependency versions (check package manifests against known CVEs), auth, sensitive data exposure, access control gaps, insecure deserialization,
missing input validation, insecure deserialization, path traversal. hardcoded secrets, vulnerable dependency versions, missing input validation,
For each finding return: CWE ID, severity (Critical/High/Med/Low), file:line, path traversal. For each finding return: CWE ID, severity
one-sentence exploit scenario, and recommended fix. Also run any available (Critical/High/Med/Low), file:line, one-sentence exploit scenario, and
SAST tooling (npm audit, pip-audit, OWASP dependency-check) and include recommended fix. Run any available SAST tooling (npm audit, pip-audit,
its raw output." OWASP dependency-check) and include its raw output."
## Triage ## Triage
@ -28,19 +31,34 @@ Write `analysis/$1/SECURITY_FINDINGS.md`:
## Remediate ## Remediate
For each **Critical** and **High** finding, fix it directly in the source. For each **Critical** and **High** finding, draft a minimal, targeted fix.
Make minimal, targeted changes. After each fix, add a one-line entry under Do **not** edit `legacy/` — write all fixes as a single unified diff to
"Remediation Log" in SECURITY_FINDINGS.md: finding ID → commit-style summary `analysis/$1/security_remediation.patch`, with a comment line above each
of what changed. hunk citing the finding ID it addresses (`# SEC-001: parameterize the query`).
Show the cumulative diff: Add a **Remediation Log** section to SECURITY_FINDINGS.md mapping each
```bash finding ID → one-line summary of the proposed fix and the patch hunk that
git -C legacy/$1 diff implements it.
```
## Verify ## Verify
Re-run the security-auditor against the patched code to confirm the Spawn the **security-auditor** again to **review the patch** against the
Critical/High findings are resolved. Update the scorecard with before/after. original code:
"Review analysis/$1/security_remediation.patch against legacy/$1. For each
hunk: does it fully remediate the cited finding? Does it introduce new
vulnerabilities or change behavior beyond the fix? Return one verdict per
hunk: RESOLVES / PARTIAL / INTRODUCES-RISK, with a one-line reason."
Add a **Patch Review** section to SECURITY_FINDINGS.md with the verdicts.
If any hunk is PARTIAL or INTRODUCES-RISK, revise the patch and re-review.
## Present
Tell the user the artifacts are ready:
- `analysis/$1/SECURITY_FINDINGS.md` — findings, remediation log, patch review
- `analysis/$1/security_remediation.patch` — review, then apply if appropriate
with `git -C legacy/$1 apply ../../analysis/$1/security_remediation.patch`
- Re-run `/modernize-harden $1` after applying to confirm resolution
Suggest: `glow -p analysis/$1/SECURITY_FINDINGS.md` Suggest: `glow -p analysis/$1/SECURITY_FINDINGS.md`

View File

@ -11,39 +11,44 @@ connect? This is the map an engineer needs before touching anything.
## What to produce ## What to produce
Write a one-off analysis script (Python or shell — your choice) that parses Write a one-off analysis script (Python or shell — your choice) that parses
the source under `legacy/$1` and extracts the four datasets below. Cover the source under `legacy/$1` and extracts the four datasets below. Three
the parse targets that are real for the stack you're looking at — these are principles apply across stacks; getting them wrong produces a misleading map:
the ones LLMs reliably miss:
- **Program/module call graph** — who calls whom. 1. **Edges live in two places** — direct calls in source, *and* dispatcher/
- COBOL/CICS: `CALL '...'` and `EXEC CICS LINK/XCTL PROGRAM(...)`. Most router calls whose targets are variables (config tables, route maps,
`PROGRAM(...)` targets are **data-names, not literals** — resolve them dependency injection, dynamic dispatch). Resolve variables against config
against working-storage `VALUE` clauses and any menu/route copybooks before declaring an edge unresolvable.
before declaring an edge unresolvable. 2. **The code↔storage join is usually external configuration**, not source —
- Java: class-level imports/invocations. Node: `require`/`import`. job/deployment descriptors map logical names to physical stores.
- **Data dependency graph** — which programs read/write which data stores. 3. **Entry points usually live in deployment config**, not source — without
- COBOL batch: `SELECT ... ASSIGN TO <ddname>` joined with JCL `DD` parsing it, every top-level module looks unreachable.
statements (this is the *only* way to attribute file I/O to a program).
- COBOL/CICS online: `EXEC CICS READ/WRITE/REWRITE/DELETE/STARTBR/READNEXT/
READPREV ... FILE(...)` joined with `DEFINE FILE` in the CSD.
- DB2: `EXEC SQL ... END-EXEC` table references — *not* JCL DD; DB2 access
is via plan/package binds.
- BMS: `SEND MAP`/`RECEIVE MAP` ↔ map source under `bms/` and copybooks
under `cpy-bms/` (or wherever the maps live).
- Java: JPA/MyBatis entities & tables. Node: model files.
- **Entry points** — whatever the stack's outermost invokers are. Mainframe:
JCL `EXEC PGM=` steps **and** CICS `DEFINE TRANSACTION ... PROGRAM(...)`
from the CSD — without the CSD, every online program looks unreachable.
Web: HTTP routes. CLI: argv parsing.
- **Dead-end candidates** — modules with no inbound edges. **Only trust this
once the entry-point and call-edge types above are all in the graph**, and
suppress the dead claim for any module that could be the target of an
unresolved dynamic call. A naive grep-only graph will mark most CICS
programs dead.
For COBOL fixed-format, slice columns 8-72 and skip `*` indicator lines Extract:
(column 7) before regex matching, or you'll match sequence numbers and
commented-out code. - **Program/module call graph** — direct calls (`CALL`, method invocations,
`import`/`require`) *and* dispatcher calls (`EXEC CICS LINK/XCTL`, DI
container wiring, framework routing, reflection/factory). Resolve variable
call targets against route tables, copybooks, config, or constant pools.
- **Data dependency graph** — which modules read/write which data stores,
joined through the relevant config: `SELECT…ASSIGN TO` ↔ JCL `DD` (batch
COBOL), `EXEC CICS READ/WRITE…FILE()` ↔ CSD `DEFINE FILE` (CICS online),
`EXEC SQL` table refs (embedded SQL), ORM annotations/mappings (Java/.NET),
model files (Node/Python/Ruby). Include UI/screen bindings (BMS maps, JSPs,
templates) — they're dependencies too.
- **Entry points** — whatever the stack's outermost invoker is, read from
where it's defined: JCL `EXEC PGM=` and CICS CSD `DEFINE TRANSACTION`
(mainframe), `web.xml`/route annotations/route files (web), `main()`/argv
parsing (CLI), queue/scheduler subscriptions (event-driven).
- **Dead-end candidates** — modules with no inbound edges. **Only meaningful
once all the entry-point and call-edge types above are in the graph.**
Suppress the dead claim for anything that could be the target of an
unresolved dynamic call. A grep-only graph will mark most dispatcher-driven
modules (CICS programs, Spring controllers, ORM-bound DAOs) dead when they
aren't.
If the source is fixed-column (COBOL columns 872, RPG, etc.), slice the
code area and strip comment lines before regex matching, or you'll match
sequence numbers and commented-out code.
Save the script as `analysis/$1/extract_topology.py` (or `.sh`) so it can be Save the script as `analysis/$1/extract_topology.py` (or `.sh`) so it can be
re-run and audited. Have it write a machine-readable re-run and audited. Have it write a machine-readable