"Phase 6: Security — scan for security issues in the feature diff. In-flow: diff-only after Review. Standalone: full repo, report-only, no state file required."
Install
npx skillscat add benkruger/flow/flow-security Install via the SkillsCat registry.
FLOW Security — Phase 6: Security
Usage
/flow:flow-security
/flow:flow-security --auto
/flow:flow-security --manual/flow:flow-security— uses configured mode from.flow.json(default: auto)/flow:flow-security --auto— auto-advance to Learning on completion/flow:flow-security --manual— prompt before advancing to Learning
- Run both commands in parallel (two Bash calls in one response):
git worktree list --porcelain— note the path on the firstworktreeline (this is the project root).git branch --show-current— this is the current branch.
- Use the Read tool to read
<project_root>/.flow-states/<branch>.json.- If the file does not exist: STOP. "BLOCKED: No FLOW feature in progress.
Run /flow:flow-start first."
- If the file does not exist: STOP. "BLOCKED: No FLOW feature in progress.
- Check
phases.flow-review.statusin the JSON.- If not
"complete": STOP. "BLOCKED: Phase 5: Review must be
complete. Run /flow:flow-review first."
- If not
Keep the project root, branch, and state data from the gate in context —
use the project root to build Read tool paths (e.g.<project_root>/.flow-states/<branch>.json). Do not re-read the state
file or re-run git commands to gather the same information. Do not cd
to the project root — bin/flow commands find paths internally.
Mode Resolution
- If
--autowas passed → commit=auto, continue=auto - If
--manualwas passed → commit=manual, continue=manual - Otherwise, read
.flow.jsonfrom the project root. Useskills.flow-security.commitandskills.flow-security.continue. - If
.flow.jsonhas noskillskey → use built-in defaults: commit=auto, continue=auto
Announce
At the very start, output the following banner in your response (not via Bash) inside a fenced code block:
```text
============================================
FLOW v0.19.0 — Phase 6: Security — STARTING
============================================
```Update State
Update state for phase entry:
bin/flow phase-transition --phase flow-security --action enterParse the JSON output to confirm "status": "ok".
If "status": "error", report the error and stop.
Logging
After every Bash command completes, log it to .flow-states/<branch>.log.
Run the command directly — do not append any suffix:
COMMANDThen Read .flow-states/<branch>.log (empty string if it does not
exist yet) and Write it back with this line appended:
YYYY-MM-DDTHH:MM:SSZ [Phase 6] Step X — desc (exit EC)Get <branch> from the state file.
Framework Instructions
Read the framework field from the state file and follow only the matching
section below for the security analysis sub-agent prompt. Do not announce
the framework — just follow the matching section silently.
If Rails
Security Analysis Sub-Agent Prompt
Provide these instructions to the Step 1 sub-agent (fill in the details):
You are analyzing a feature diff for security issues in a Ruby on Rails
application.
Feature:Tool rules: Use Glob and Read tools for all file and directory
operations. Use Grep for searching code. Only use Bash for git commands
(git diff, git log, git blame, git show). Never pipe git output through
sed, grep, awk, or any other command — read the full output and extract
what you need in your response. Never usecd <path> && git— run git
commands from the current directory. Never use Bash for any other
purpose — no find, ls, cat, wc, test -f, stat, or running project tooling.Plan file:
<paste the plan file contents — includes approach and risks>First, get the full diff:
git diff origin/main...HEADRead every changed file completely. Then run each of these 10 security
checks against the diff. For each check, report either a finding (with
file path and line number) or mark it clean.Check 1: Authorization gaps (
authorization_gaps)
Look for controller actions added or modified in the diff that have nobefore_actionauthentication/authorization filter. Check whether
existing auth filters are skipped for new actions.
Vulnerable pattern: a new action with no auth filter covering it
Vulnerable pattern:skip_before_action :require_login, only: [:new_action]Check 2: Unscoped record access (
unscoped_access)
Look for record lookups that useparams[:id](or similar) without
scoping to the current user, account, or tenant.
Vulnerable:Record::Base.find(params[:id])
Safe:current_account.records.find(params[:id])Check 3: Mass assignment (
mass_assignment)
Look forparams.permit!(permits everything), overly broadpermit
calls that include admin-only or internal fields, or params hashes
passed directly tocreate!/update!withoutpermit.
Vulnerable:Record::Create.create!(params[:record])
Vulnerable:params.require(:record).permit!
Vulnerable:permit(:role, :admin, :internal_flag)when user-facingCheck 4: SQL injection (
sql_injection)
Look for string interpolation or concatenation insidewhere,order,select,group,having,pluck,execute,find_by_sql, orActiveRecord::Base.connectioncalls.
Vulnerable:where("name = '#{params[:name]}'")
Vulnerable:order("#{params[:sort]} #{params[:dir]}")
Safe:where(name: params[:name])
Safe:where("name = ?", params[:name])Check 5: Data exposure (
data_exposure)
Look for sensitive fields (password, token, secret, ssn, credit card,
api_key) included inas_json,to_json, serializer attributes,
or API responses. Check for PII logged viaRails.loggerorputs.
Check for credentials or secrets hardcoded in source.
Vulnerable:render json: user.as_json
Vulnerable:Rails.logger.info("User #{user.email} token: #{user.token}")Check 6: CSRF bypass (
csrf_bypass)
Look forskip_before_action :verify_authenticity_token. This is only
acceptable on API-only controllers that use token auth instead of
cookies. If the controller serves browser requests with cookie auth,
this is a finding.
Vulnerable:skip_before_action :verify_authenticity_tokenon a
controller that uses session/cookie authCheck 7: Open redirects (
open_redirects)
Look forredirect_towhere the URL comes from user input (params,
form fields, headers). Attackers use this for phishing.
Vulnerable:redirect_to params[:return_url]
Vulnerable:redirect_to request.referer
Safe:redirect_to root_path
Safe:redirect_to params[:return_url], allow_other_host: falseCheck 8: RuboCop disables (
rubocop_disables)
Look for any# rubocop:disablecomment anywhere in the diff. This is
an automatic finding — no judgment needed. Every disable must be
removed and the underlying code fixed.Check 9: Auth test coverage (
auth_test_coverage)
If the diff adds abefore_actionauth check or an authorization
gate, look for a corresponding test that verifies the unauthorized
or forbidden case. A new auth check without a test for the reject
path is a finding.Check 10: Route exposure (
route_exposure)
Look for new routes (inconfig/routes.rborconfig/routes/*.rb)
that point to controller actions. Read the target controller and
verify it has an auth filter covering the routed action. A route to
an unprotected action is a finding.Return your findings as two lists:
Findings — for each issue found:
- Check name and key (e.g., "Authorization gaps" /
authorization_gaps)- Description of the specific issue
- File path and line number
Clean checks — list the check keys that found no issues.
If no issues are found across all checks, say so explicitly and list
all 10 checks as clean.
If Python
Security Analysis Sub-Agent Prompt
Provide these instructions to the Step 1 sub-agent (fill in the details):
You are analyzing a feature diff for security issues in a Python
application.
Feature:Tool rules: Use Glob and Read tools for all file and directory
operations. Use Grep for searching code. Only use Bash for git commands
(git diff, git log, git blame, git show). Never pipe git output through
sed, grep, awk, or any other command — read the full output and extract
what you need in your response. Never usecd <path> && git— run git
commands from the current directory. Never use Bash for any other
purpose — no find, ls, cat, wc, test -f, stat, or running project tooling.Plan file:
<paste the plan file contents — includes approach and risks>First, get the full diff:
git diff origin/main...HEADRead every changed file completely. Then run each of these 10 security
checks against the diff. For each check, report either a finding (with
file path and line number) or mark it clean.Check 1: Command injection (
command_injection)
Look forsubprocess.run,subprocess.call,os.system,os.popen, orPopenwhere command arguments come from user input
or external data without proper escaping.
Vulnerable:subprocess.run(f"echo {user_input}", shell=True)
Safe:subprocess.run(["echo", user_input])Check 2: Path traversal (
path_traversal)
Look for file operations where the path includes user input without
validation. Check for../traversal or absolute path injection.
Vulnerable:open(f"uploads/{filename}")
Safe:path.resolve().relative_to(base_dir)Check 3: Input validation (
input_validation)
Look for external inputs (CLI args, environment variables, file
contents, API responses) used without validation or sanitization.
Vulnerable:int(sys.argv[1])without try/except
Vulnerable:os.environ["SECRET"]without fallbackCheck 4: Data exposure (
data_exposure)
Look for sensitive data (passwords, tokens, secrets, API keys) logged,
printed, or written to files. Check for credentials hardcoded in source.
Vulnerable:print(f"Token: {token}")
Vulnerable:API_KEY = "sk-abc123"Check 5: Unsafe deserialization (
unsafe_deserialization)
Look forpickle.load,yaml.load(without SafeLoader),eval,exec, or__import__on untrusted data.
Vulnerable:pickle.load(user_file)
Vulnerable:yaml.load(data)(withoutLoader=SafeLoader)
Safe:json.loads(data)Check 6: Dependency security (
dependency_security)
Look for new dependencies added without version pinning, or known
vulnerable versions. Checkrequirements.txt,pyproject.toml.
Vulnerable:requests(no version pin)
Safe:requests>=2.31.0Check 7: Error information leakage (
error_leakage)
Look for exception handling that exposes internal details (stack
traces, file paths, database queries) to external consumers.
Vulnerable:return str(e)in an API response
Safe:return "Internal error"with logging of the full exceptionCheck 8: Lint suppression (
lint_suppression)
Look for any# noqa,# type: ignore, or# pragma: no cover
comment in the diff. Each is a finding — remove it and fix the
underlying issue.Check 9: Temporary file safety (
temp_file_safety)
Look foropen("/tmp/...")or predictable temporary file names.
Vulnerable:open("/tmp/myapp_data.txt", "w")
Safe:tempfile.NamedTemporaryFile()Check 10: Permission and access (
permission_access)
Look for file permission changes,chmod, or files created with
overly permissive modes. Check foros.chmod(path, 0o777)or
similar.Return your findings as two lists:
Findings — for each issue found:
- Check name and key (e.g., "Command injection" /
command_injection)- Description of the specific issue
- File path and line number
Clean checks — list the check keys that found no issues.
If no issues are found across all checks, say so explicitly and list
all 10 checks as clean.
Step 1 — Launch security analysis sub-agent
Read plan_file from the state file to get the plan file path. Use the
Read tool to read the plan file — it contains the approach and risks.
Then launch a mandatory sub-agent to analyze the feature diff for security
issues. Use the Task tool:
subagent_type:"general-purpose"description:"Security analysis"
Provide the sub-agent with the Security Analysis Sub-Agent Prompt from the
framework section above (fill in the feature name and plan file contents).
Wait for the sub-agent to return before proceeding.
Step 2 — Confirm findings and record in state
Read the sub-agent's findings. For each reported issue:
- Read the cited file and line to confirm the issue exists (sub-agents may
have false positives) - Drop any finding that is a false positive — explain why it was dropped
Write all confirmed findings and clean checks to the state file:
"security": {
"findings": [
{
"id": 1,
"check": "<check_name>",
"description": "<what was found and where>",
"file": "<path/to/affected_file>",
"line": 15,
"status": "pending"
}
],
"clean_checks": ["<check_1>", "<check_2>", "<check_3>"],
"scanned_at": null
}Check names and categories are defined by the framework section above.
Number each finding with a sequential id. Set status to "pending" for
every confirmed finding. Set scanned_at to null in the object you write.
If there are no confirmed findings, set findings to an empty array, list
all 10 checks in clean_checks, and skip to Step 4.
How to update: Read .flow-states/<branch>.json, parse the JSON,
modify the fields in memory, then use the Write tool to write the
entire file back. Never use the Edit tool for state file changes —
field names repeat across phases and cause non-unique match errors.
Then set the scan timestamp:
bin/flow set-timestamp --set security.scanned_at=NOWStep 3 — Fix findings
Fix each confirmed finding one at a time, in order:
- Fix the issue in code
- Run
bin/flow ci --if-dirty - If commit=auto, invoke
/flow:flow-commit --autofor the fix. Otherwise invoke/flow:flow-commit. - Update the finding's
statusto"fixed"in the state file - Move to the next finding
How to update: Read .flow-states/<branch>.json, parse the JSON,
modify the fields in memory, then use the Write tool to write the
entire file back. Never use the Edit tool for state file changes —
field names repeat across phases and cause non-unique match errors.
Repeat until all findings have status: "fixed".
Step 4 — Present security summary
Show a summary of what was found and fixed inside a fenced code block:
```text
============================================
FLOW — Phase 6: Security — SUMMARY
============================================
Checks run : 10
Findings : N
Fixed : N
Clean checks : N
Findings
--------
- [FIXED] <check_name>: <description of finding>
- [FIXED] <check_name>: <description of finding>
Clean Checks
------------
<check_1>, <check_2>, <check_3>, ...
bin/flow ci : ✓ green
============================================
```Done — Update state and complete phase
Complete the phase:
bin/flow phase-transition --phase flow-security --action completeParse the JSON output. If "status": "error", report the error and stop.
Use the formatted_time field in the COMPLETE banner below. Do not print
the timing calculation.
Output in your response (not via Bash) inside a fenced code block:
```text
============================================
FLOW v0.19.0 — Phase 6: Security — COMPLETE (<formatted_time>)
============================================
```Invoke flow:flow-status.
If continue=auto, skip the transition question and invoke flow:flow-learning directly.
If continue=manual, use AskUserQuestion:
"Phase 6: Security is complete. Ready to begin Phase 7: Learning?"
- Yes, start Phase 7 now — invoke
flow:flow-learning- Not yet — print paused banner
- I have a correction or learning to capture
If "I have a correction or learning to capture":
- Ask the user what they want to capture
- Invoke
/flow:flow-notewith their message - Re-ask with only "Yes, start Phase 7 now" and "Not yet"
If Yes — invoke flow:flow-learning using the Skill tool.
If Not yet, output in your response (not via Bash) inside a fenced code block:
```text
============================================
FLOW — Paused
Run /flow:flow-continue when ready to continue.
============================================
```Hard Rules
- Always run
bin/flow ciafter any fix made during Security - Never transition to Learning unless
bin/flow ciis green - Read the full diff before starting — no partial reviews
- Never use Bash to print banners — output them as text in your response
- Never use Bash for file reads — use Glob, Read, and Grep tools instead of ls, cat, head, tail, find, or grep
- Never use
cd <path> && git— usegit -C <path>for git commands in other directories - Never cd before running
bin/flow— it detects the project root internally