Skip to content

Commit 4c87aca

Browse files
author
Ramachandran R
committed
feat(security-review): integrate scan-code skill for local source and package vulnerability checks
- Added scan-code skill to scan local Power Pages projects for dependency vulnerabilities and code issues using npm audit and ESLint. - Updated security-review skill to incorporate scan-code findings alongside live-site checks. - Enhanced HTML report generation to include separate sections for package and code findings with verbatim severities. - Modified existing styles and severity labels in the security review report template to accommodate new severities. - Documented the scan-code skill with detailed usage instructions and workflow.
1 parent 062d950 commit 4c87aca

8 files changed

Lines changed: 798 additions & 26 deletions

File tree

plugins/power-pages/references/skill-tracking-reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ If the tracking script creates or updates site setting YAML files, include those
4141
| add-cloud-flow | AddCloudFlow | Site/AI/Skills/AddCloudFlow |
4242
| integrate-backend | IntegrateBackend | Site/AI/Skills/IntegrateBackend |
4343
| scan-site | ScanSite | Site/AI/Skills/ScanSite |
44+
| scan-code | ScanCode | Site/AI/Skills/ScanCode |
4445
| manage-headers | ManageHeaders | Site/AI/Skills/ManageHeaders |
4546
| manage-firewall | ManageFirewall | Site/AI/Skills/ManageFirewall |
4647
| security-review | SecurityReview | Site/AI/Skills/SecurityReview |

plugins/power-pages/scripts/build-review-data.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,19 @@ if (!fs.existsSync(inputDir)) {
6060
}
6161

6262
const SECTION_MAP = {
63-
'scan-site.json': { id: 'site-scan', label: 'Live Site Scan', icon: '◐' },
64-
'manage-headers.json': { id: 'headers', label: 'Browser Headers', icon: '◑' },
65-
'manage-firewall.json': { id: 'firewall', label: 'Web Application Firewall', icon: '◆' },
66-
'audit-permissions.json': { id: 'permissions', label: 'Roles & Permissions', icon: '◇' },
67-
'setup-auth.json': { id: 'auth', label: 'Access & Identity', icon: '◈' },
63+
'scan-code-eslint.json': { id: 'code-issues', label: 'Code Issues', icon: '◍' },
64+
'scan-code-packages.json': { id: 'code-packages', label: 'Package Vulnerabilities', icon: '◉' },
65+
'scan-site.json': { id: 'site-scan', label: 'Live Site Scan', icon: '◐' },
66+
'manage-headers.json': { id: 'headers', label: 'Browser Headers', icon: '◑' },
67+
'manage-firewall.json': { id: 'firewall', label: 'Web Application Firewall', icon: '◆' },
68+
'audit-permissions.json': { id: 'permissions', label: 'Roles & Permissions', icon: '◇' },
69+
'setup-auth.json': { id: 'auth', label: 'Access & Identity', icon: '◈' },
6870
};
6971

7072
// Severities that may appear on findings — ordered by precedence (most severe first).
71-
// pass is excluded from the "issue" count but still shown as its own stat.
72-
const SEVERITIES = ['critical', 'high', 'warning', 'medium', 'info', 'low', 'pass'];
73+
// Tools may emit any of these strings as-is: ESLint uses error/warning, npm audit uses
74+
// critical/high/moderate/low/info. pass is excluded from the "issue" count.
75+
const SEVERITIES = ['critical', 'high', 'error', 'warning', 'medium', 'moderate', 'info', 'low', 'pass'];
7376

7477
const sections = [];
7578
const totals = Object.fromEntries(SEVERITIES.map(s => [s, 0]));

plugins/power-pages/scripts/lib/templates/security-review-report.html

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@
5858
.stat-label{font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;margin-top:6px;}
5959

6060
.severity{font-size:9px;font-weight:700;padding:3px 8px;border-radius:3px;font-family:var(--mono);display:inline-block;text-transform:uppercase;letter-spacing:0.5px;}
61-
.severity-critical,.severity-high{color:var(--critical);background:var(--critical-bg);border:1px solid var(--critical-border);}
62-
.severity-warning,.severity-medium{color:var(--warning);background:var(--warning-bg);border:1px solid var(--warning-border);}
61+
.severity-critical,.severity-high,.severity-error{color:var(--critical);background:var(--critical-bg);border:1px solid var(--critical-border);}
62+
.severity-warning,.severity-medium,.severity-moderate{color:var(--warning);background:var(--warning-bg);border:1px solid var(--warning-border);}
6363
.severity-info,.severity-low{color:var(--info);background:var(--info-bg);border:1px solid var(--info-border);}
6464
.severity-pass{color:var(--pass);background:var(--pass-bg);border:1px solid var(--pass-border);}
6565

@@ -145,7 +145,7 @@
145145
// Like esc(), but renders newlines as <br> for multi-line details/fix text.
146146
function escMultiline(s) { return esc(s).replace(/\n/g, '<br>'); }
147147
function escAttr(s) { return String(s || '').replace(/[^a-zA-Z0-9_.-]/g, ''); }
148-
const sevLabel = (s) => ({ critical:'CRITICAL', high:'HIGH', warning:'WARNING', medium:'MEDIUM', info:'INFO', low:'LOW', pass:'PASSED' }[s] || (s ? s.toUpperCase() : ''));
148+
const sevLabel = (s) => ({ critical:'CRITICAL', high:'HIGH', error:'ERROR', warning:'WARNING', medium:'MEDIUM', moderate:'MODERATE', info:'INFO', low:'LOW', pass:'PASSED' }[s] || (s ? s.toUpperCase() : ''));
149149

150150
function findingHtml(f, idx) {
151151
const id = escAttr(f.id || ('f' + idx));
@@ -187,7 +187,7 @@
187187

188188
function sectionHtml(s) {
189189
// Sort only when at least one finding has severity; otherwise preserve insertion order (inventory sections).
190-
const order = { critical:0, high:0, warning:1, medium:1, info:2, low:2, pass:3 };
190+
const order = { critical:0, high:0, error:0, warning:1, medium:1, moderate:1, info:2, low:2, pass:3 };
191191
const findings = (s.findings || []).some(f => f.severity)
192192
? (s.findings || []).slice().sort((a, b) => (order[a.severity] ?? 99) - (order[b.severity] ?? 99))
193193
: (s.findings || []);
@@ -202,18 +202,20 @@
202202
}
203203

204204
// Stat-card metadata for every severity that may appear on findings.
205-
// Same precedence ordering used everywhere (critical → high → warning → medium → info → low → pass).
205+
// Same precedence ordering used everywhere (critical/error → high → warning/moderate → medium → info → low → pass).
206206
const STAT_CARDS = [
207207
{ key: 'critical', label: 'Critical', color: 'var(--critical)' },
208208
{ key: 'high', label: 'High', color: 'var(--critical)' },
209+
{ key: 'error', label: 'Error', color: 'var(--critical)' },
209210
{ key: 'warning', label: 'Warning', color: 'var(--warning)' },
210211
{ key: 'medium', label: 'Medium', color: 'var(--warning)' },
212+
{ key: 'moderate', label: 'Moderate', color: 'var(--warning)' },
211213
{ key: 'info', label: 'Info', color: 'var(--info)' },
212214
{ key: 'low', label: 'Low', color: 'var(--info)' },
213215
{ key: 'pass', label: 'Passed', color: 'var(--pass)' },
214216
];
215217
// Severities that count as "issues" for the sidebar badge.
216-
const ISSUE_KEYS = new Set(['critical', 'high', 'warning', 'medium']);
218+
const ISSUE_KEYS = new Set(['critical', 'high', 'error', 'warning', 'medium', 'moderate']);
217219

218220
// Overview section
219221
function overviewHtml() {
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
---
2+
name: scan-code
3+
description: >-
4+
Scans a Power Pages code-site project for dependency vulnerabilities
5+
(`npm audit`) and JavaScript/TypeScript code issues (ESLint), installs
6+
the matching ESLint plugins for the detected framework (React, Vue,
7+
Angular, Astro), and produces a single HTML report with package and
8+
code findings shown in separate sections. Severities are kept verbatim
9+
from the underlying tools. Use when the user wants to scan local
10+
source code, check dependencies, lint the project, audit packages,
11+
find code vulnerabilities, or run a pre-publish code check — even if
12+
they say "check my code" or "find security issues in my source"
13+
without naming `npm audit` or ESLint.
14+
user-invocable: true
15+
argument-hint: "[optional: --review <out-dir>]"
16+
allowed-tools: Read, Write, Bash, Glob, Grep, AskUserQuestion, TaskCreate, TaskUpdate, TaskList
17+
model: opus
18+
---
19+
20+
> **Plugin check**: Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding.
21+
22+
# Scan Code
23+
24+
Scan the local Power Pages code-site project for two classes of issues:
25+
26+
- **Package vulnerabilities**`npm audit` against `package.json` / `package-lock.json`.
27+
- **Code issues** — ESLint against project source, with framework-aware plugins.
28+
29+
The skill writes both result sets and renders one HTML report with the two scans as separate sections. Findings carry the **verbatim severity** emitted by each tool (npm audit: `critical`/`high`/`moderate`/`low`/`info`; ESLint: `error`/`warning`).
30+
31+
This skill scans local source code only. It does not call the live site.
32+
33+
**Initial request:** $ARGUMENTS
34+
35+
## Gotchas
36+
37+
- **`npm audit` needs a lockfile.** Without `package-lock.json` it cannot resolve transitive versions — the audit section is reported as `skipped` and the user is asked to run `npm install` first.
38+
- **ESLint workspace is bootstrapped in `<projectRoot>/.scan-code/`.** Plugins are installed there (not in the user's `package.json`) to keep the project clean. The folder ships a `.gitignore` of `*` so it stays untracked. Deleting it forces a fresh install on the next run.
39+
- **Severities are not remapped.** The HTML template understands every native severity these tools emit. Do not rewrite severity strings — pass them through.
40+
- **Server-rendered frameworks are not supported.** Power Pages code sites only support React, Vue, Angular, and Astro. Other projects produce a `skipped` lint section.
41+
42+
## Workflow
43+
44+
1. **Prerequisites** — locate project, detect review mode
45+
2. **Run package audit**`npm audit``scan-code-packages.json`
46+
3. **Run code lint** — detect framework, install ESLint plugins, lint → `scan-code-eslint.json`
47+
4. **Render or hand off** — review mode: stop; interactive mode: render HTML
48+
5. **Walk through follow-ups** — only if findings exist and not in review mode
49+
50+
## Task tracking
51+
52+
Create tasks in three groups. Mark each `in_progress` when starting, `completed` when done.
53+
54+
| Group | When | Tasks |
55+
|-------|------|-------|
56+
| 1 | At start | Check prerequisites |
57+
| 2 | After prerequisites pass | Run package audit · Run code lint |
58+
| 3 | After both scans complete (interactive only) | Render report · Walk through follow-ups |
59+
60+
In review mode, skip Group 3 — the orchestrator owns rendering.
61+
62+
---
63+
64+
## 1. Prerequisites
65+
66+
### 1.1 Locate the project, detect review mode
67+
68+
Use `Glob` to find `**/powerpages.config.json`. If none is found, tell the user the site needs to be created first with `/create-site`, then stop.
69+
70+
If `$ARGUMENTS` contains `--review <out-dir>`, remember the output directory. Step 4 becomes "write JSON only" and Step 5 is skipped.
71+
72+
### 1.2 Verify the project has a `package.json`
73+
74+
Confirm `<PROJECT_ROOT>/package.json` exists. If not, this is not a code-site project — tell the user, then stop.
75+
76+
### 1.3 Pick the output paths
77+
78+
| Mode | Package output | Lint output |
79+
|------|----------------|-------------|
80+
| Review | `<REVIEW_DIR>/scan-code-packages.json` | `<REVIEW_DIR>/scan-code-eslint.json` |
81+
| Interactive | `<SYSTEM_TEMP>/scan-code/scan-code-packages.json` | `<SYSTEM_TEMP>/scan-code/scan-code-eslint.json` |
82+
83+
Create the directory if it does not exist. In interactive mode, delete any prior contents of `<SYSTEM_TEMP>/scan-code/` first.
84+
85+
---
86+
87+
## 2. Run the package audit
88+
89+
```bash
90+
node "${CLAUDE_PLUGIN_ROOT}/skills/scan-code/scripts/audit.js" \
91+
--projectRoot "<PROJECT_ROOT>" \
92+
--output "<PACKAGE_OUTPUT_PATH>"
93+
```
94+
95+
The script:
96+
97+
- Runs `npm audit --json` in `<PROJECT_ROOT>`.
98+
- Transforms each `vulnerabilities` entry into a finding (one per advisory cluster).
99+
- Severities are kept verbatim from npm (`critical`, `high`, `moderate`, `low`, `info`).
100+
- Returns `{ "status": "skipped", "reason": "..." }` if `package-lock.json` is missing or npm itself errors.
101+
102+
Tell the user, in one sentence, that the package audit completed (and how many issues if any).
103+
104+
---
105+
106+
## 3. Run the code lint
107+
108+
```bash
109+
node "${CLAUDE_PLUGIN_ROOT}/skills/scan-code/scripts/lint.js" \
110+
--projectRoot "<PROJECT_ROOT>" \
111+
--output "<LINT_OUTPUT_PATH>"
112+
```
113+
114+
What the script does on first run:
115+
116+
1. Reads `<PROJECT_ROOT>/package.json` to detect the framework — React, Vue, Angular, or Astro.
117+
2. Writes `<PROJECT_ROOT>/.scan-code/package.json` with the matching ESLint + plugin set.
118+
3. Runs `npm install --prefix <PROJECT_ROOT>/.scan-code/` to install them.
119+
4. Writes `<PROJECT_ROOT>/.scan-code/eslint.config.mjs` with framework-aware rules **and an embedded ignore list** for `node_modules`, `.powerpages-site`, `dist`, `build`, `docs`, `coverage`, `public`, `.scan-code`, and minified bundles.
120+
5. Runs ESLint over `src/**/*` (with framework extensions) plus root-level config files only.
121+
6. Emits findings with severities kept verbatim (`error`, `warning`).
122+
123+
Subsequent runs reuse the workspace. Pass `--reinstall` to force re-provisioning.
124+
125+
If no supported framework is detected, the script returns `{ "status": "skipped", ... }` — surface that as a single info finding in the report.
126+
127+
---
128+
129+
## 4. Render or hand off
130+
131+
### 4.1 Review mode — hand off and stop
132+
133+
If invoked with `--review <out-dir>`, the two JSON files are already in place. Stop — the orchestrator (`/security-review`) consolidates them into the master report.
134+
135+
### 4.2 Interactive mode — render the HTML report
136+
137+
Compose a 1–2 sentence plain-language summary covering totals across both sections, then render:
138+
139+
```bash
140+
node "${CLAUDE_PLUGIN_ROOT}/scripts/build-review-data.js" \
141+
--reportName "Code Scan" \
142+
--inputDir "<SYSTEM_TEMP>/scan-code/" \
143+
--siteName "<SITE_NAME>" \
144+
--goalLabel "Source code & packages" \
145+
--scopeLabel "<PROJECT_ROOT>" \
146+
--summary "<SUMMARY_TEXT>" \
147+
--output "<SYSTEM_TEMP>/scan-code/data.json"
148+
149+
node "${CLAUDE_PLUGIN_ROOT}/scripts/render-review.js" \
150+
--output "<PROJECT_ROOT>/docs/scan-code-<YYYY-MM-DD-HHMMSS>.html" \
151+
--data "<SYSTEM_TEMP>/scan-code/data.json"
152+
```
153+
154+
The filename **must** include the local timestamp (e.g. `scan-code-2026-05-22-141530.html`). Open the rendered HTML in the user's default browser.
155+
156+
Delete `<SYSTEM_TEMP>/scan-code/` after rendering succeeds.
157+
158+
### 4.3 Record skill usage
159+
160+
> Reference: `${CLAUDE_PLUGIN_ROOT}/references/skill-tracking-reference.md`
161+
>
162+
> Use `--skillName "ScanCode"`.
163+
164+
---
165+
166+
## 5. Walk through follow-ups
167+
168+
Skip in **review mode**. Skip when both sections have zero issues.
169+
170+
Group findings by remedy:
171+
172+
- **Package vulnerabilities with auto-fix** → tell the user the exact `npm audit fix` invocation reported in each finding's `fix` field.
173+
- **Package vulnerabilities without auto-fix** → recommend reviewing the advisory link and updating manually.
174+
- **ESLint errors / warnings** → suggest running `eslint --fix` in the project for auto-fixable issues, then reviewing the rest by file.
175+
176+
If nothing meaningful applies, end the skill — do not ask just to ask.
177+
178+
---
179+
180+
## Constraints
181+
182+
- **Severities are verbatim.** Do not rewrite, bucket, or rename severity strings. The shared HTML template knows every value these tools emit.
183+
- **Source code only.** Never lint `node_modules`, `.powerpages-site`, `docs`, `dist`, `build`, `coverage`, `public`, or `.scan-code/` itself. The generated ESLint config carries these ignores.
184+
- **Do not modify the user's `package.json`.** ESLint and its plugins live in the side workspace at `<projectRoot>/.scan-code/`.
185+
- **No new HTML template.** Render via the shared template under `scripts/lib/templates/`. Package and code findings are routed through separate JSON files so they appear as two sidebar sections.

0 commit comments

Comments
 (0)