| name | kedro-plugins-security-review |
| description | Run a security scan on the kedro-plugins codebase or a pull request. This skill runs Semgrep with dataset-specific rules, triages findings against the dataset trust model, audits nosec suppressions, and produces one final report. Use when the user says things like "run security scan on the full codebase" or "run security scan on this PR". Produce one final report only, in chat by default or posted to GitHub when explicitly asked. |
Security Scan
Before scanning, decide two things:
- Target source — full codebase or PR (see
## Mode selection)
- Delivery — chat (default) or post to GitHub (see
## Delivery modes)
Then run the numbered Workflow below:
- detect which Semgrep binary is available (see Defaults)
- create a temporary working directory with
mktemp -d
- determine what to scan — full repo or changed files from the PR
- run Semgrep in parallel across all rulesets
- read and deduplicate findings
- triage and classify findings against the dataset trust model
- run manual review checks from
reference.md
- emit one final report
References
Read these before triaging findings:
Defaults
- Runtime:
semgrep
- otherwise
uvx --from semgrep semgrep
- otherwise
uv tool run --from semgrep semgrep
- Rulesets:
p/security-audit
p/secrets
p/python
.agents/skills/kedro-plugins-security-review/rules/kedro-plugins-security-patterns.yml
- Path exclusions (every Semgrep invocation). Note: this is a monorepo, so
tests/, docs/, and features/ are nested under each plugin directory
(e.g. kedro-datasets/tests/). Use glob patterns that match at any depth:
--exclude '**/tests/**'
--exclude '**/docs/**'
--exclude '**/features/**'
--exclude '.agents/'
--exclude 'tools/'
--exclude '**/kedro_datasets_benchmarks/**'
- Temporary working directory:
- create with
mktemp -d
- delete at the end unless the user explicitly asks to keep artifacts
Always use --metrics=off.
Runtime note
If a uvx or uv tool run Semgrep command fails with a permissions error on a
~/.cache/uv path, ask the user for permission to run outside the sandbox
(i.e. with full filesystem access) and then rerun the same command. Do not
treat a cache permission failure as a real scan failure.
Later workflow steps that invoke Semgrep should refer back to this note instead
of repeating it.
Delivery modes
Chat mode
Default mode. Show one final report in chat and do not expose intermediate scan
output.
Post mode
Only use when the user explicitly asks to post, submit, or publish the scan
result to GitHub.
Post mode is only valid in PR mode — it produces a GitHub PR review payload
and there is no PR to attach it to in full-codebase mode. If a user asks to
post a full-codebase scan, fall back to chat mode and tell the user why.
In post mode:
- still do all scanning and triage locally first
- construct one final GitHub review payload
- post it after the review is complete
Do not post partial results or progress updates.
Mode selection
Full codebase mode
Use when the user asks to scan the repo, codebase, project, or current branch
without narrowing to a PR.
Target:
kedro-datasets/ by default (this is where all dataset code lives)
- if the user explicitly asks to scan a different plugin (e.g.
kedro-docker),
scan that directory instead — but note that the triage guidance and manual
review checks are designed for dataset code and may not apply
PR mode
Use when the user asks to scan a PR or when the request names a PR number or
URL.
Resolve the PR from:
- the explicit PR number or URL if provided
- otherwise the current branch via
gh pr view
Then get the changed files:
gh pr diff <number> --name-only
Only keep files that still exist in the working tree. Scan just those files.
If the PR has no scannable files, report that clearly and stop.
Workflow
1. Resolve runtime
if command -v semgrep >/dev/null 2>&1; then
ENGINE_LABEL="Semgrep OSS (host CLI)"
SEMGREP_CMD=(semgrep)
elif command -v uvx >/dev/null 2>&1; then
ENGINE_LABEL="Semgrep OSS (uvx)"
SEMGREP_CMD=(uvx --from semgrep semgrep)
elif command -v uv >/dev/null 2>&1; then
ENGINE_LABEL="Semgrep OSS (uv tool run)"
SEMGREP_CMD=(uv tool run --from semgrep semgrep)
else
echo "ERROR: neither semgrep, uvx, nor uv is available."
exit 1
fi
SEMGREP_EXCLUDE=(
--exclude '**/tests/**'
--exclude '**/docs/**'
--exclude '**/features/**'
--exclude '.agents/'
--exclude 'tools/'
--exclude '**/kedro_datasets_benchmarks/**'
)
"${SEMGREP_CMD[@]}" --version
If the version check fails, apply the Runtime note above before treating
the scan as failed.
2. Resolve temporary working directory
Check whether the user explicitly asked to keep artifacts before emitting
the script below.
If NOT keeping artifacts:
OUTPUT_DIR="$(mktemp -d "${TMPDIR:-/tmp}/kedro-plugins-security-review.XXXXXX")"
cleanup() {
rm -rf "$OUTPUT_DIR"
}
trap cleanup EXIT
mkdir -p "$OUTPUT_DIR/raw"
If keeping artifacts (omit the trap entirely):
OUTPUT_DIR="$(mktemp -d "${TMPDIR:-/tmp}/kedro-plugins-security-review.XXXXXX")"
mkdir -p "$OUTPUT_DIR/raw"
In that case, report $OUTPUT_DIR at the end of the scan so the user can
inspect it.
3. Resolve scan target
For full codebase mode:
SCAN_TARGETS=(kedro-datasets/)
TARGET_LABEL="kedro-datasets (full codebase)"
For PR mode:
PR_NUMBER="<resolved-pr-number>"
SCAN_TARGETS=()
while IFS= read -r path; do
[ -f "$path" ] || continue
skip=0
for segment in /tests/ /docs/ /features/ /kedro_datasets_benchmarks/; do
[[ "/$path" == *"$segment"* ]] && skip=1 && break
done
[[ "$path" == .agents/* ]] && skip=1
[[ "$path" == tools/* ]] && skip=1
(( skip == 0 )) && SCAN_TARGETS+=("$path")
done < <(gh pr diff "$PR_NUMBER" --name-only)
TARGET_LABEL="PR #$PR_NUMBER"
If SCAN_TARGETS is empty, stop and report that there are no scannable files
(this also happens when a PR only touches excluded paths like tests/ or
docs/).
4. Run Semgrep
Set:
LOCAL_RULESET="$(pwd)/.agents/skills/kedro-plugins-security-review/rules/kedro-plugins-security-patterns.yml"
Run these in parallel (each command includes "${SEMGREP_EXCLUDE[@]}"):
(
"${SEMGREP_CMD[@]}" scan --metrics=off \
"${SEMGREP_EXCLUDE[@]}" \
--config p/security-audit \
--json --output "$OUTPUT_DIR/raw/security-audit.json" \
"${SCAN_TARGETS[@]}"
) &
(
"${SEMGREP_CMD[@]}" scan --metrics=off \
"${SEMGREP_EXCLUDE[@]}" \
--config p/secrets \
--json --output "$OUTPUT_DIR/raw/secrets.json" \
"${SCAN_TARGETS[@]}"
) &
(
"${SEMGREP_CMD[@]}" scan --metrics=off \
"${SEMGREP_EXCLUDE[@]}" \
--include="*.py" --config p/python \
--json --output "$OUTPUT_DIR/raw/python.json" \
"${SCAN_TARGETS[@]}"
) &
(
"${SEMGREP_CMD[@]}" scan --metrics=off \
"${SEMGREP_EXCLUDE[@]}" \
--include="*.py" \
--config "$LOCAL_RULESET" \
--json --output "$OUTPUT_DIR/raw/kedro-plugins-security-patterns.json" \
"${SCAN_TARGETS[@]}"
) &
wait
If one or more rulesets fail, continue with the findings from the rulesets that
did succeed. Do not abort the full scan. In the final report, include a
"Scan errors" section that lists each failed ruleset and its error message so
the user knows the scan was partial.
If a scan command fails, apply the Runtime note before marking that
ruleset as failed.
5. Read findings
Read all JSON files in $OUTPUT_DIR/raw/ directly. Deduplicate by
(check_id, path, start.line) — if the same finding appears in more than one
ruleset output, count it once.
6. Triage against the dataset trust model
For every unique finding:
- open the flagged file and inspect the surrounding code
- classify it using reference.md
- include the Suggested next step from the matching bucket in
reference.md
Use these buckets:
dataset_vulnerability
by_design_with_documentation
needs_manual_review
7. Manual review checks (always run, even with zero Semgrep findings)
After triaging Semgrep output, run the Manual review checks from
reference.md against the scan target.
For each check:
- Search the scanned files for the pattern described
- If found and unmitigated, add it to the findings list with classification
dataset_vulnerability or needs_manual_review
- If not found, the check was clean — do not itemise it
When all checks are clean, report them as a single line in the final report
rather than enumerating each one. Only expand a check when it actually
flagged something.
This step exists because Semgrep only catches known patterns. These checks
catch the class of issues that static analysis misses.
8. Build the final report
Do not stream intermediate findings to the user.
Accumulate findings during triage, then produce exactly one final report using
the format below.
If post mode was requested, write a single review JSON payload and post it via:
bash .agents/scripts/post_github_review.sh <review_json_file>
Delete the temporary review JSON file after posting.
Reporting
Keep the report short and decisive. The classification summary already carries
the counts — do not repeat non-actionable findings as prose.
Always include:
- mode used: full codebase or PR
- target scanned
- total Semgrep findings reviewed
- counts by classification bucket
- highest Semgrep severities present
Only itemise actionable findings in the Findings section:
- Always:
dataset_vulnerability, needs_manual_review
Do not itemise findings classified as by_design_with_documentation —
they are reflected in the classification summary counts and that is sufficient.
If the Findings section has nothing actionable, replace it with a single line
that names the non-actionable counts (e.g. "None actionable. 3 by-design
findings on pre-existing lines.").
For each actionable finding, include:
- file
- line
- rule id
- Semgrep severity
- classification
- one-sentence reasoning
- suggested next step (from
reference.md for that bucket; for
dataset_vulnerability, pick the ERROR or WARNING recommendation based on
Semgrep severity)
If no findings are plausible dataset vulnerabilities, say so explicitly.
Output format
Chat mode
Return one final report in this shape:
## Kedro Datasets Security Scan
> Generated with `kedro-plugins-security-review`.
### Overview
- **Mode:** <full codebase | PR>
- **Target:** <repo root | PR #123>
- **Findings reviewed:** <count>
- **Highest Semgrep severities:** <list or "none">
### Classification summary
- **dataset_vulnerability:** <count>
- **needs_manual_review:** <count>
- **by_design_with_documentation:** <count>
### Findings
<For each actionable finding (dataset_vulnerability, needs_manual_review):>
- `path/to/file.py:L42` — `<classification>` — <rule > — —
None actionable. by-design findings on pre-existing lines.
All clean (load
Post mode
Write one GitHub review payload:
{
"event": "COMMENT",
"body": "## Kedro Datasets Security Scan\n...(final summary report)...",
"comments": [
{
"path": "file.py",
"line": 42,
"side": "RIGHT",
"body": "**dataset_vulnerability:** <reason>\n\nRule: `<rule id>`\nSeverity: `<severity>`\nNext step: <next step>"
}
]
}
Use inline comments only for actionable findings tied to changed PR lines:
dataset_vulnerability
needs_manual_review
Do not post inline comments for by_design_with_documentation. Put the full
summary in body, following the same conciseness rules as chat mode.