Security review pass 1: scope, threat model, attack-surface map, first candidate findings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
51
devdocs/modernization/security/routemap.py
Normal file
51
devdocs/modernization/security/routemap.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# Map every controller action: HTTP verb + route, anonymous or not, and which checks its body makes.
|
||||
# Usage: python -I routemap.py <Controllers folder> <out.tsv>
|
||||
import os, re, sys
|
||||
|
||||
src, out = sys.argv[1], sys.argv[2]
|
||||
verb_re = re.compile(r'\[Http(Get|Post|Put|Delete|Patch)(?:\("([^"]*)"\))?\]')
|
||||
method_re = re.compile(r'public\s+(?:async\s+)?[\w<>\[\], ]+\s+(\w+)\s*\(')
|
||||
checks = {
|
||||
'role': re.compile(r'Authorized\.Has\w+Role|HasAnyRole|HasReadFullRole|HasModifyRole|HasCreateRole|HasDeleteRole|HasSelectRole'),
|
||||
'usertype': re.compile(r'UserTypeFromContext|UserType\.Customer|UserType\.HeadOffice|IsCustomerUser|CustomerUser'),
|
||||
'biz': re.compile(r'Biz\.GetBiz|new \w+Biz\(|\w+Biz\.'),
|
||||
'dltoken': re.compile(r'ValidateDownloadToken'),
|
||||
}
|
||||
|
||||
rows = []
|
||||
for fn in sorted(os.listdir(src)):
|
||||
if not fn.endswith('.cs'):
|
||||
continue
|
||||
text = open(os.path.join(src, fn), encoding='utf-8-sig').read()
|
||||
# drop // comments so commented-out actions are ignored
|
||||
text = '\n'.join(l for l in text.splitlines() if not l.lstrip().startswith('//'))
|
||||
m = re.search(r'\[Route\("([^"]*)"\)\]', text)
|
||||
base = m.group(1).replace('api/v{version:apiVersion}/', '') if m else ''
|
||||
cls_auth = bool(re.search(r'\[Authorize\][^{]*class\s+\w+Controller', text, re.S))
|
||||
for vm in verb_re.finditer(text):
|
||||
after = text[vm.end():]
|
||||
mm = method_re.search(after)
|
||||
if not mm:
|
||||
continue
|
||||
attrs = text[max(0, vm.start() - 300):vm.end()] + after[:mm.start()]
|
||||
anon = '[AllowAnonymous]' in attrs
|
||||
# body: from the method's opening brace to the matching closing brace
|
||||
start = vm.end() + mm.end()
|
||||
brace = text.find('{', start)
|
||||
depth, i = 0, brace
|
||||
while i < len(text):
|
||||
if text[i] == '{': depth += 1
|
||||
elif text[i] == '}':
|
||||
depth -= 1
|
||||
if depth == 0: break
|
||||
i += 1
|
||||
body = text[brace:i]
|
||||
found = [k for k, r in checks.items() if r.search(body)]
|
||||
rows.append((fn.replace('Controller.cs', ''), vm.group(1).upper(), (base + '/' + (vm.group(2) or '')).rstrip('/'),
|
||||
mm.group(1), 'ANON' if anon else ('auth' if cls_auth or '[Authorize]' in attrs else 'NO-ATTR'), ','.join(found) or '-'))
|
||||
|
||||
with open(out, 'w', encoding='utf-8') as f:
|
||||
f.write('controller\tverb\troute\taction\taccess\tchecks\n')
|
||||
for r in rows:
|
||||
f.write('\t'.join(r) + '\n')
|
||||
print(len(rows), 'actions')
|
||||
Reference in New Issue
Block a user