As I get more into white box testing of code bases, I started looking for tools that run static code analysis. A lot of teams I work with believe that GitHub is doing that for them, as they get alerts on their codebases. What is often triggered, however, is not scans of their primary code but the front-end JS. Currently, GitHub’s code scanning works for a limited set of languages. While that will likely improve in the future, if you use a language set that is out of scope, you may want to find a solution to catch bugs and vulnerabilities earlier in the life-cycle.
My Goal
After taking Offensive Security’s WEB-300, I wanted to use the knowledge gained to identify issues early on. Getting security based coverage, and finding potential fail points at the code level was the goal. Rather than just testing black box, I wanted to get insight into the code and find fail points or spot potential vulnerable areas for further testing.
Why I Wrote My Own
I had a need to test two different code bases, each written in a framework or base language that GitHub’s code scanning didn’t support. Looking for solutions, I only found a few and they were all at various price tiers.
Many solutions out there also require a grant of access to a GitHub repo, which is then scanned. This is beyond my comfort level, so I wanted a solution that ran locally on the file system.
I also wanted a report that was unique to my needs.
I wrote a framework that has these parts:
- Loads a folder of code off the file system
- Recursively goes through each file, by file type
- Uses a set of regex written for OWASP top ten, concepts learned from WEB-300, regression, new issues found in testing, etc.
- Once done, it compiles an HTML report for easy review and sorting of results.
A workable demonstration version of my script is available on GitHub (looking for OWASP top 10 for the most part), and is currently set up with a base set of rules for a ColdFusion codebase. Rules can be modified/added on to support a variety of code bases, but this is a public demo of the script:
Code Overview
The script is written in Python for easy of prototyping this concept.
Currently the project has a script per language or framework that I’m working with. This public version of my project is an illustration of what it’s capable of doing. It is setup to work with Coldfusion and Java. Yes, Java is covered by GitHub code analysis, but ColdFusion is not. Both are included to compare results to GitHub’s code analysis.
The main script, looks like this:
import argparse
import json
import csv
import re
import sys
from pathlib import Path
from typing import Dict, List, Iterable
import report_writer
import rules
run_rules = rules.ruleset("cf")
CF_EXTS = {".cfm", ".cfml", ".cfc", ".html"}
def compile_rules() -> None:
"""Compile regexes once and store on each pattern dict."""
for rule in run_rules:
for pat in rule["patterns"]:
pat["_compiled"] = re.compile(pat["regex"], re.I | re.S)
def find_files(root: Path) -> Iterable[Path]:
for p in root.rglob("*"):
# skip non-files and files with the marker anywhere in the name
if not p.is_file():
continue
if p.suffix.lower() in CF_EXTS:
yield p
def get_context(lines: List[str], idx: int) -> str:
"""Return previous, current, next line with numbers for quick view."""
snip = []
for rel in (-1, 0, 1):
j = idx + rel
if 0 <= j < len(lines):
snip.append(f"{j+1:>4}| {lines[j].rstrip()}")
return "\n".join(snip)
def analyse_file(path: Path) -> List[Dict]:
findings = []
try:
text = path.read_text(encoding="utf-8", errors="ignore")
except Exception as e:
return [{
"file": str(path),
"line": 0,
"rule": "I/O‑ERROR",
"severity": "high",
"match": "",
"reason": f"Unable to read file: {e}",
"recommendation": "Fix file encoding/permissions and re‑scan.",
"context": ""
}]
lines = text.splitlines()
for i, line in enumerate(lines):
for rule in run_rules:
for pat in rule["patterns"]:
m = pat["_compiled"].search(line)
if m:
findings.append({
"file": str(path.relative_to(Path.cwd())),
"line": i + 1,
"rule": f"{rule['id']}: {rule['name']}",
"severity": pat["severity"],
"match": m.group(0).strip()[:120], # truncate long matches
"reason": pat["reason"],
"recommendation": pat["recommendation"],
"context": get_context(lines, i)
})
return findings
def main() -> None:
parser = argparse.ArgumentParser(
description="ColdFusion static analyzer with detailed explanations"
)
parser.add_argument(
"directory",
nargs="?",
default=".",
help="Directory root to scan (default: current working dir)"
)
parser.add_argument(
"-o",
"--output",
default="findings.csv",
help="CSV file to write (default: findings.csv)",
)
parser.add_argument(
"--html-report",
metavar="FILE",
help="Write results as HTML report to this file",
)
parser.add_argument(
"-d",
metavar="FILE",
help="Deeper analysis",
)
args = parser.parse_args()
root = Path(args.directory).expanduser().resolve()
if not root.exists():
sys.exit(f"[!] Path not found: {root}")
compile_rules()
all_findings: List[Dict] = []
cf_files = list(find_files(root))
for fp in cf_files:
all_findings.extend(analyse_file(fp))
# Emit JSON to stdout
print(json.dumps(all_findings, indent=2))
# --------- write CSV ----------
fieldnames = [
"file",
"line",
"rule",
"severity",
"match",
"reason",
"recommendation",
"context",
]
try:
with open(args.output, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(all_findings)
except Exception as e:
sys.exit(f"[!] Failed to write CSV: {e}")
# Friendly summary to stderr
summary: Dict[str, int] = {}
for f in all_findings:
summary[f["rule"]] = summary.get(f["rule"], 0) + 1
sys.stderr.write("\n=== Summary ===\n")
for rule, count in sorted(summary.items(), key=lambda x: (-x[1], x[0])):
sys.stderr.write(f"{rule:<35} {count}\n")
sys.stderr.write(
f"Scanned {len(cf_files)} file(s) — total findings: {len(all_findings)}\n"
)
if args.html_report:
report_writer.write_html_report(all_findings, args.html_report, "cf")
sys.stderr.write(f"HTML report written to {args.html_report}\n")
if __name__ == "__main__":
main()
Reading it from the top down, the script starts off setting the tone of what we’re testing:
run_rules = rules.ruleset("cf")
CF_EXTS = {".cfm", ".cfml", ".cfc", ".html"}
In this demo example we’re looking at ColdFusion files, so I’m passing the “cf” parameter to the ruleset file (where all the regex rules are stored). CF_EXTS identifies each file extension to test. We could add .js to bring in other front-end results, but in this demo, it’s mostly CF focused.
The compile_rules method brings in the appropirate rules for the code base being scanned.
The find_files method is iterating over the directory it’s being run in. It scans for any files that are in the CF_EXTS set.
The get_context method is a report focused method. In the report we output a hit… like “in this file, on this line, there’s this potential vulnerability…” It helps to know context. There is a context field that pulls in the lines of code above and below the hit.
In analyse_file, this is where the work happens. It compares the compiled rules with each file. Each file that is pulled in, from the code base, is run through this analysis method. It will flag any line that triggers on a regex pattern (which is defined in the rules file.)
In the main method of the script it sets up some argument it will accept to trigger different responses. In my case with this demo script, I have it take -o to output a CSV report, — html-report to output a HTML report. The reports are outputing fields that were setup earlier on.
Regex Rules
When I started I worked with only the OWASP Top 10. I tried to identify rules that fit alpha-numeric OWASP codes… like “A01: Broken Access Controls”.
As I am not fluent in these different code bases, I either searched for “OWASP top 10 [language/framework name]”, or I asked ChatGPT for regex patterns to find specific security flaws in code with regex patterns.
As time went on, I would spot areas of the code I was manually testing, that had bugs or vulnerabilities. As new issues were found, and could be tied to a bit of code, the further regex patterns were added to the rules.
The rules file has entries like:
{
"id": "A01",
"name": "Broken Access Control",
"patterns": [
_p(
r"<cffile\s+action\s*=\s*\"?write\"?",
"File‑write operation without explicit authorization guard.",
"Ensure user is authorized; validate and sanitise path.",
"high"
),
_p(
r"<cfcomponent[^>]*\baccess\s*=",
"Component declares broad access level; may expose methods.",
"Restrict access attribute (remote/public/package) as narrowly as possible."
)
]
}
These patterns are using a regex pattern to find, why it is conerning, advice on what to do, and a severity level. The main script compiled these rules and added those components into the reporting output, if a line was matched the pattern.
In the above example, I’m looking for any spot in code where a <cffile…> element is set to write a file to the file system. If found, it’s flagged for someone to review. We need to make sure any reference like that is authorized and to test around breaking said authorization to violate the upload policy, upload infected files to test AV, and so on.
Expansion of the Script
The script could be used on any codebase, for any regex trigger. It could also be written in a more OO way so that the ruleset is a class, and breaking out some of the core functionality in the script into different classes and methods as well.



