codesift-mcp 0.4.0 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -25
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +8 -6
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/platform.d.ts.map +1 -1
- package/dist/cli/platform.js +12 -14
- package/dist/cli/platform.js.map +1 -1
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +30 -6
- package/dist/cli/setup.js.map +1 -1
- package/dist/formatters.d.ts +2 -2
- package/dist/formatters.d.ts.map +1 -1
- package/dist/formatters.js +23 -0
- package/dist/formatters.js.map +1 -1
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.d.ts.map +1 -1
- package/dist/instructions.js +21 -21
- package/dist/parser/extractors/php.d.ts.map +1 -1
- package/dist/parser/extractors/php.js +37 -29
- package/dist/parser/extractors/php.js.map +1 -1
- package/dist/parser/extractors/typescript.d.ts.map +1 -1
- package/dist/parser/extractors/typescript.js +43 -0
- package/dist/parser/extractors/typescript.js.map +1 -1
- package/dist/parser/parse-cache.d.ts +39 -0
- package/dist/parser/parse-cache.d.ts.map +1 -0
- package/dist/parser/parse-cache.js +87 -0
- package/dist/parser/parse-cache.js.map +1 -0
- package/dist/parser/parser-manager.d.ts +1 -1
- package/dist/parser/parser-manager.d.ts.map +1 -1
- package/dist/parser/parser-manager.js +14 -5
- package/dist/parser/parser-manager.js.map +1 -1
- package/dist/parser/stub-languages.d.ts +2 -0
- package/dist/parser/stub-languages.d.ts.map +1 -0
- package/dist/parser/stub-languages.js +5 -0
- package/dist/parser/stub-languages.js.map +1 -0
- package/dist/register-tool-loaders.d.ts +130 -0
- package/dist/register-tool-loaders.d.ts.map +1 -0
- package/dist/register-tool-loaders.js +212 -0
- package/dist/register-tool-loaders.js.map +1 -0
- package/dist/register-tools.d.ts +2 -2
- package/dist/register-tools.d.ts.map +1 -1
- package/dist/register-tools.js +355 -634
- package/dist/register-tools.js.map +1 -1
- package/dist/search/tool-ranker.d.ts +90 -0
- package/dist/search/tool-ranker.d.ts.map +1 -0
- package/dist/search/tool-ranker.js +420 -0
- package/dist/search/tool-ranker.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +23 -14
- package/dist/server.js.map +1 -1
- package/dist/storage/usage-tracker.d.ts.map +1 -1
- package/dist/storage/usage-tracker.js +4 -1
- package/dist/storage/usage-tracker.js.map +1 -1
- package/dist/tools/astro-actions.d.ts +54 -0
- package/dist/tools/astro-actions.d.ts.map +1 -0
- package/dist/tools/astro-actions.js +561 -0
- package/dist/tools/astro-actions.js.map +1 -0
- package/dist/tools/astro-audit.d.ts +87 -0
- package/dist/tools/astro-audit.d.ts.map +1 -0
- package/dist/tools/astro-audit.js +345 -0
- package/dist/tools/astro-audit.js.map +1 -0
- package/dist/tools/astro-content-collections.d.ts +44 -0
- package/dist/tools/astro-content-collections.d.ts.map +1 -0
- package/dist/tools/astro-content-collections.js +630 -0
- package/dist/tools/astro-content-collections.js.map +1 -0
- package/dist/tools/astro-islands.d.ts +3 -1
- package/dist/tools/astro-islands.d.ts.map +1 -1
- package/dist/tools/astro-islands.js +19 -4
- package/dist/tools/astro-islands.js.map +1 -1
- package/dist/tools/astro-migration.d.ts +31 -0
- package/dist/tools/astro-migration.d.ts.map +1 -0
- package/dist/tools/astro-migration.js +378 -0
- package/dist/tools/astro-migration.js.map +1 -0
- package/dist/tools/async-correctness.d.ts +26 -0
- package/dist/tools/async-correctness.d.ts.map +1 -0
- package/dist/tools/async-correctness.js +166 -0
- package/dist/tools/async-correctness.js.map +1 -0
- package/dist/tools/django-view-security-tools.d.ts +32 -0
- package/dist/tools/django-view-security-tools.d.ts.map +1 -0
- package/dist/tools/django-view-security-tools.js +184 -0
- package/dist/tools/django-view-security-tools.js.map +1 -0
- package/dist/tools/fastapi-depends.d.ts +63 -0
- package/dist/tools/fastapi-depends.d.ts.map +1 -0
- package/dist/tools/fastapi-depends.js +191 -0
- package/dist/tools/fastapi-depends.js.map +1 -0
- package/dist/tools/hono-analyze-app.js +1 -9
- package/dist/tools/hono-analyze-app.js.map +1 -1
- package/dist/tools/hono-api-contract.d.ts.map +1 -1
- package/dist/tools/hono-api-contract.js +41 -9
- package/dist/tools/hono-api-contract.js.map +1 -1
- package/dist/tools/hono-context-flow.js +1 -9
- package/dist/tools/hono-context-flow.js.map +1 -1
- package/dist/tools/hono-dead-routes.d.ts.map +1 -1
- package/dist/tools/hono-dead-routes.js +2 -9
- package/dist/tools/hono-dead-routes.js.map +1 -1
- package/dist/tools/hono-entry-resolver.d.ts +27 -0
- package/dist/tools/hono-entry-resolver.d.ts.map +1 -0
- package/dist/tools/hono-entry-resolver.js +31 -0
- package/dist/tools/hono-entry-resolver.js.map +1 -0
- package/dist/tools/hono-inline-analyze.js +1 -9
- package/dist/tools/hono-inline-analyze.js.map +1 -1
- package/dist/tools/hono-middleware-chain.d.ts +24 -6
- package/dist/tools/hono-middleware-chain.d.ts.map +1 -1
- package/dist/tools/hono-middleware-chain.js +77 -40
- package/dist/tools/hono-middleware-chain.js.map +1 -1
- package/dist/tools/hono-modules.js +1 -9
- package/dist/tools/hono-modules.js.map +1 -1
- package/dist/tools/hono-response-types.js +1 -9
- package/dist/tools/hono-response-types.js.map +1 -1
- package/dist/tools/hono-rpc-types.js +1 -9
- package/dist/tools/hono-rpc-types.js.map +1 -1
- package/dist/tools/hono-security.d.ts +14 -4
- package/dist/tools/hono-security.d.ts.map +1 -1
- package/dist/tools/hono-security.js +185 -14
- package/dist/tools/hono-security.js.map +1 -1
- package/dist/tools/hono-visualize.js +1 -9
- package/dist/tools/hono-visualize.js.map +1 -1
- package/dist/tools/nest-ext-tools.d.ts +115 -0
- package/dist/tools/nest-ext-tools.d.ts.map +1 -1
- package/dist/tools/nest-ext-tools.js +393 -0
- package/dist/tools/nest-ext-tools.js.map +1 -1
- package/dist/tools/nest-tools.d.ts +27 -0
- package/dist/tools/nest-tools.d.ts.map +1 -1
- package/dist/tools/nest-tools.js +137 -37
- package/dist/tools/nest-tools.js.map +1 -1
- package/dist/tools/nextjs-component-readers.d.ts +101 -0
- package/dist/tools/nextjs-component-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-component-readers.js +287 -0
- package/dist/tools/nextjs-component-readers.js.map +1 -0
- package/dist/tools/nextjs-component-tools.d.ts +8 -78
- package/dist/tools/nextjs-component-tools.d.ts.map +1 -1
- package/dist/tools/nextjs-component-tools.js +9 -257
- package/dist/tools/nextjs-component-tools.js.map +1 -1
- package/dist/tools/nextjs-framework-audit-tools.d.ts +24 -1
- package/dist/tools/nextjs-framework-audit-tools.d.ts.map +1 -1
- package/dist/tools/nextjs-framework-audit-tools.js +184 -1
- package/dist/tools/nextjs-framework-audit-tools.js.map +1 -1
- package/dist/tools/nextjs-route-readers.d.ts +81 -0
- package/dist/tools/nextjs-route-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-route-readers.js +340 -0
- package/dist/tools/nextjs-route-readers.js.map +1 -0
- package/dist/tools/nextjs-route-tools.d.ts +7 -71
- package/dist/tools/nextjs-route-tools.d.ts.map +1 -1
- package/dist/tools/nextjs-route-tools.js +9 -327
- package/dist/tools/nextjs-route-tools.js.map +1 -1
- package/dist/tools/pattern-tools.d.ts.map +1 -1
- package/dist/tools/pattern-tools.js +92 -2
- package/dist/tools/pattern-tools.js.map +1 -1
- package/dist/tools/php-tools.d.ts +14 -5
- package/dist/tools/php-tools.d.ts.map +1 -1
- package/dist/tools/php-tools.js +166 -64
- package/dist/tools/php-tools.js.map +1 -1
- package/dist/tools/plan-turn-tools.d.ts +89 -0
- package/dist/tools/plan-turn-tools.d.ts.map +1 -0
- package/dist/tools/plan-turn-tools.js +508 -0
- package/dist/tools/plan-turn-tools.js.map +1 -0
- package/dist/tools/project-tools.d.ts +1 -1
- package/dist/tools/project-tools.js +1 -1
- package/dist/tools/project-tools.js.map +1 -1
- package/dist/tools/pydantic-models.d.ts +46 -0
- package/dist/tools/pydantic-models.d.ts.map +1 -0
- package/dist/tools/pydantic-models.js +249 -0
- package/dist/tools/pydantic-models.js.map +1 -0
- package/dist/tools/python-audit.d.ts +40 -0
- package/dist/tools/python-audit.d.ts.map +1 -0
- package/dist/tools/python-audit.js +244 -0
- package/dist/tools/python-audit.js.map +1 -0
- package/dist/tools/python-constants-tools.d.ts +44 -0
- package/dist/tools/python-constants-tools.d.ts.map +1 -0
- package/dist/tools/python-constants-tools.js +525 -0
- package/dist/tools/python-constants-tools.js.map +1 -0
- package/dist/tools/react-tools.d.ts +46 -1
- package/dist/tools/react-tools.d.ts.map +1 -1
- package/dist/tools/react-tools.js +126 -1
- package/dist/tools/react-tools.js.map +1 -1
- package/dist/tools/review-diff-tools.d.ts +5 -0
- package/dist/tools/review-diff-tools.d.ts.map +1 -1
- package/dist/tools/review-diff-tools.js +109 -3
- package/dist/tools/review-diff-tools.js.map +1 -1
- package/dist/tools/search-tools.d.ts +3 -2
- package/dist/tools/search-tools.d.ts.map +1 -1
- package/dist/tools/search-tools.js +16 -3
- package/dist/tools/search-tools.js.map +1 -1
- package/dist/tools/sql-tools.d.ts +40 -0
- package/dist/tools/sql-tools.d.ts.map +1 -1
- package/dist/tools/sql-tools.js +123 -0
- package/dist/tools/sql-tools.js.map +1 -1
- package/dist/tools/symbol-tools.d.ts.map +1 -1
- package/dist/tools/symbol-tools.js +7 -10
- package/dist/tools/symbol-tools.js.map +1 -1
- package/dist/tools/taint-tools.d.ts +43 -0
- package/dist/tools/taint-tools.d.ts.map +1 -0
- package/dist/tools/taint-tools.js +922 -0
- package/dist/tools/taint-tools.js.map +1 -0
- package/dist/utils/import-graph.d.ts +6 -0
- package/dist/utils/import-graph.d.ts.map +1 -1
- package/dist/utils/import-graph.js +43 -7
- package/dist/utils/import-graph.js.map +1 -1
- package/package.json +3 -3
- package/rules/codesift.md +51 -13
- package/rules/codesift.mdc +51 -13
- package/rules/codex.md +51 -13
- package/rules/gemini.md +51 -13
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* analyze_async_correctness — detect async/await bugs in Python code.
|
|
3
|
+
*
|
|
4
|
+
* Finds 8 common asyncio pitfalls that cause production issues but are
|
|
5
|
+
* silent at test time:
|
|
6
|
+
*
|
|
7
|
+
* 1. blocking-requests — requests.get() inside async def (use httpx)
|
|
8
|
+
* 2. blocking-sleep — time.sleep() in async def (use asyncio.sleep)
|
|
9
|
+
* 3. blocking-io — open()/read() in async (use aiofiles)
|
|
10
|
+
* 4. sync-db-in-async — sync SQLAlchemy/Django ORM in async view
|
|
11
|
+
* 5. missing-await — coroutine expression used as regular value
|
|
12
|
+
* 6. async-without-await — async def with no await (probably unnecessary)
|
|
13
|
+
* 7. blocking-subprocess — subprocess.run/call in async (use asyncio.subprocess)
|
|
14
|
+
* 8. globalscope-task — asyncio.create_task without storing ref (GC loss)
|
|
15
|
+
*
|
|
16
|
+
* Uses symbol graph: walks each async function's source text for the
|
|
17
|
+
* patterns above. Returns file/line/symbol/pattern/suggested fix.
|
|
18
|
+
*/
|
|
19
|
+
import { getCodeIndex } from "./index-tools.js";
|
|
20
|
+
const CHECKS = [
|
|
21
|
+
{
|
|
22
|
+
rule: "blocking-requests",
|
|
23
|
+
severity: "error",
|
|
24
|
+
regex: /\brequests\.(get|post|put|delete|patch|head|options|request)\s*\(/,
|
|
25
|
+
message: "requests library is synchronous — blocks the event loop",
|
|
26
|
+
fix: "Use httpx.AsyncClient: async with httpx.AsyncClient() as client: await client.get(url)",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
rule: "blocking-sleep",
|
|
30
|
+
severity: "error",
|
|
31
|
+
regex: /\btime\.sleep\s*\(/,
|
|
32
|
+
message: "time.sleep() blocks the event loop, freezes all concurrent tasks",
|
|
33
|
+
fix: "Use await asyncio.sleep(seconds) instead",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
rule: "blocking-io",
|
|
37
|
+
severity: "warning",
|
|
38
|
+
regex: /(?<!async\s)\bopen\s*\([^)]*\)(?!\s*async)/,
|
|
39
|
+
message: "open() and file I/O block the event loop on slow disks",
|
|
40
|
+
fix: "Use aiofiles.open() for async file I/O, or run_in_executor for heavy I/O",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
rule: "sync-db-in-async",
|
|
44
|
+
severity: "error",
|
|
45
|
+
regex: /\b(?:session|db)\.(query|execute|commit|flush|add)\s*\(/,
|
|
46
|
+
message: "Synchronous SQLAlchemy session blocks the event loop",
|
|
47
|
+
fix: "Use AsyncSession: await session.execute(stmt), async with session.begin()",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
rule: "sync-orm-django",
|
|
51
|
+
severity: "error",
|
|
52
|
+
regex: /\.objects\.(?:get|filter|all|count|exists|create|update|delete)\s*\(/,
|
|
53
|
+
message: "Synchronous Django ORM blocks the event loop in async views",
|
|
54
|
+
fix: "Use async ORM methods (.aget(), .acount(), .aall()) or wrap with sync_to_async",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
rule: "blocking-subprocess",
|
|
58
|
+
severity: "error",
|
|
59
|
+
regex: /\bsubprocess\.(run|call|check_output|check_call|Popen)\s*\(/,
|
|
60
|
+
message: "subprocess is synchronous — blocks the event loop",
|
|
61
|
+
fix: "Use asyncio.create_subprocess_exec() or asyncio.create_subprocess_shell()",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
rule: "globalscope-task",
|
|
65
|
+
severity: "warning",
|
|
66
|
+
regex: /(?<!=\s)\basyncio\.create_task\s*\(/,
|
|
67
|
+
message: "asyncio.create_task() without storing the returned Task — may be garbage collected before completion",
|
|
68
|
+
fix: "Store in a set: tasks.add(asyncio.create_task(...)); tasks.discard on done",
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
/**
|
|
72
|
+
* Check for async def with no await in body — possibly unnecessary async.
|
|
73
|
+
* Handled separately from the CHECKS array because it requires symbol-level
|
|
74
|
+
* analysis (not just a regex on source).
|
|
75
|
+
*/
|
|
76
|
+
function isAsyncWithoutAwait(sym) {
|
|
77
|
+
if (!sym.is_async)
|
|
78
|
+
return false;
|
|
79
|
+
const source = sym.source ?? "";
|
|
80
|
+
// Strip docstring to avoid false positive on "await" in doc
|
|
81
|
+
const withoutDocstring = source.replace(/"""[\s\S]*?"""/g, "").replace(/'''[\s\S]*?'''/g, "");
|
|
82
|
+
// If the function body has no await, async with, or async for, flag it
|
|
83
|
+
return !/\bawait\b/.test(withoutDocstring)
|
|
84
|
+
&& !/\basync\s+with\b/.test(withoutDocstring)
|
|
85
|
+
&& !/\basync\s+for\b/.test(withoutDocstring)
|
|
86
|
+
&& !/\byield\b/.test(withoutDocstring); // async generators need yield
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Analyze async correctness across all async Python functions.
|
|
90
|
+
*/
|
|
91
|
+
export async function analyzeAsyncCorrectness(repo, options) {
|
|
92
|
+
const index = await getCodeIndex(repo);
|
|
93
|
+
if (!index)
|
|
94
|
+
throw new Error(`Repository "${repo}" not found.`);
|
|
95
|
+
const filePattern = options?.file_pattern;
|
|
96
|
+
const enabled = new Set(options?.rules ?? [
|
|
97
|
+
...CHECKS.map((c) => c.rule),
|
|
98
|
+
"async-without-await",
|
|
99
|
+
]);
|
|
100
|
+
const maxResults = options?.max_results ?? 200;
|
|
101
|
+
const findings = [];
|
|
102
|
+
let asyncFunctionCount = 0;
|
|
103
|
+
for (const sym of index.symbols) {
|
|
104
|
+
if (findings.length >= maxResults)
|
|
105
|
+
break;
|
|
106
|
+
if (!sym.file.endsWith(".py"))
|
|
107
|
+
continue;
|
|
108
|
+
if (filePattern && !sym.file.includes(filePattern))
|
|
109
|
+
continue;
|
|
110
|
+
if (!sym.is_async)
|
|
111
|
+
continue;
|
|
112
|
+
asyncFunctionCount++;
|
|
113
|
+
const source = sym.source ?? "";
|
|
114
|
+
// async-without-await check
|
|
115
|
+
if (enabled.has("async-without-await") && isAsyncWithoutAwait(sym)) {
|
|
116
|
+
findings.push({
|
|
117
|
+
rule: "async-without-await",
|
|
118
|
+
severity: "info",
|
|
119
|
+
file: sym.file,
|
|
120
|
+
line: sym.start_line,
|
|
121
|
+
symbol: sym.name,
|
|
122
|
+
symbol_kind: sym.kind,
|
|
123
|
+
match: `async def ${sym.name}`,
|
|
124
|
+
message: "async def with no await in body — may be unnecessarily async",
|
|
125
|
+
fix: "Remove async if not needed, or add real await operations",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Pattern-based checks
|
|
129
|
+
for (const check of CHECKS) {
|
|
130
|
+
if (!enabled.has(check.rule))
|
|
131
|
+
continue;
|
|
132
|
+
check.regex.lastIndex = 0;
|
|
133
|
+
const m = check.regex.exec(source);
|
|
134
|
+
if (!m)
|
|
135
|
+
continue;
|
|
136
|
+
// Compute line offset within the symbol
|
|
137
|
+
const lineOffset = source.slice(0, m.index).split("\n").length - 1;
|
|
138
|
+
// Extract the matching line
|
|
139
|
+
const lineStart = source.lastIndexOf("\n", m.index) + 1;
|
|
140
|
+
const lineEnd = source.indexOf("\n", m.index);
|
|
141
|
+
const matchLine = source.slice(lineStart, lineEnd === -1 ? undefined : lineEnd).trim();
|
|
142
|
+
findings.push({
|
|
143
|
+
rule: check.rule,
|
|
144
|
+
severity: check.severity,
|
|
145
|
+
file: sym.file,
|
|
146
|
+
line: sym.start_line + lineOffset,
|
|
147
|
+
symbol: sym.name,
|
|
148
|
+
symbol_kind: sym.kind,
|
|
149
|
+
match: matchLine.slice(0, 200),
|
|
150
|
+
message: check.message,
|
|
151
|
+
fix: check.fix,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const by_rule = {};
|
|
156
|
+
for (const f of findings) {
|
|
157
|
+
by_rule[f.rule] = (by_rule[f.rule] ?? 0) + 1;
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
findings,
|
|
161
|
+
total: findings.length,
|
|
162
|
+
by_rule,
|
|
163
|
+
async_functions_scanned: asyncFunctionCount,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=async-correctness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-correctness.js","sourceRoot":"","sources":["../../src/tools/async-correctness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CodeSymbol } from "../types.js";
|
|
2
|
+
export interface DjangoViewSecurityAssessment {
|
|
3
|
+
symbol_name: string;
|
|
4
|
+
symbol_kind: CodeSymbol["kind"];
|
|
5
|
+
file: string;
|
|
6
|
+
line: number;
|
|
7
|
+
route_path?: string;
|
|
8
|
+
view_type: "function" | "class" | "method";
|
|
9
|
+
decorators: string[];
|
|
10
|
+
mixins: string[];
|
|
11
|
+
auth_guards: string[];
|
|
12
|
+
csrf_exempt: boolean;
|
|
13
|
+
effective_auth_required: boolean;
|
|
14
|
+
csrf_protected: boolean;
|
|
15
|
+
authentication_middleware: boolean;
|
|
16
|
+
session_middleware: boolean;
|
|
17
|
+
security_middleware: boolean;
|
|
18
|
+
notes: string[];
|
|
19
|
+
confidence: "high" | "medium" | "low";
|
|
20
|
+
}
|
|
21
|
+
export interface DjangoViewSecurityResult {
|
|
22
|
+
assessments: DjangoViewSecurityAssessment[];
|
|
23
|
+
settings_files: string[];
|
|
24
|
+
middleware: string[];
|
|
25
|
+
}
|
|
26
|
+
export declare function effectiveDjangoViewSecurity(repo: string, options: {
|
|
27
|
+
path?: string;
|
|
28
|
+
symbol_name?: string;
|
|
29
|
+
file_pattern?: string;
|
|
30
|
+
settings_file?: string;
|
|
31
|
+
}): Promise<DjangoViewSecurityResult>;
|
|
32
|
+
//# sourceMappingURL=django-view-security-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"django-view-security-tools.d.ts","sourceRoot":"","sources":["../../src/tools/django-view-security-tools.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAa,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzD,MAAM,WAAW,4BAA4B;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC3C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,uBAAuB,EAAE,OAAO,CAAC;IACjC,cAAc,EAAE,OAAO,CAAC;IACxB,yBAAyB,EAAE,OAAO,CAAC;IACnC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;CACvC;AAED,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,4BAA4B,EAAE,CAAC;IAC5C,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AA8KD,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IACP,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACA,OAAO,CAAC,wBAAwB,CAAC,CA+BnC"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getCodeIndex } from "./index-tools.js";
|
|
4
|
+
import { traceRoute } from "./route-tools.js";
|
|
5
|
+
const AUTH_DECORATORS = [
|
|
6
|
+
"login_required",
|
|
7
|
+
"permission_required",
|
|
8
|
+
"user_passes_test",
|
|
9
|
+
"staff_member_required",
|
|
10
|
+
"superuser_required",
|
|
11
|
+
];
|
|
12
|
+
const AUTH_MIXINS = [
|
|
13
|
+
"LoginRequiredMixin",
|
|
14
|
+
"PermissionRequiredMixin",
|
|
15
|
+
"UserPassesTestMixin",
|
|
16
|
+
"AccessMixin",
|
|
17
|
+
];
|
|
18
|
+
function hasDecorator(decorators, name) {
|
|
19
|
+
return decorators.some((decorator) => {
|
|
20
|
+
const normalized = decorator.trim().replace(/^@/, "");
|
|
21
|
+
return normalized === name || normalized.startsWith(`${name}(`);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function collectAuthGuards(decorators, mixins) {
|
|
25
|
+
const guards = new Set();
|
|
26
|
+
for (const decorator of AUTH_DECORATORS) {
|
|
27
|
+
if (hasDecorator(decorators, decorator))
|
|
28
|
+
guards.add(decorator);
|
|
29
|
+
}
|
|
30
|
+
for (const mixin of AUTH_MIXINS) {
|
|
31
|
+
if (mixins.includes(mixin))
|
|
32
|
+
guards.add(mixin);
|
|
33
|
+
}
|
|
34
|
+
return [...guards];
|
|
35
|
+
}
|
|
36
|
+
function getSettingsFiles(index, explicitSettingsFile) {
|
|
37
|
+
if (explicitSettingsFile)
|
|
38
|
+
return [explicitSettingsFile];
|
|
39
|
+
return index.files
|
|
40
|
+
.filter((file) => file.path.endsWith(".py"))
|
|
41
|
+
.filter((file) => /\/settings\.py$|\/settings\/[\w_]+\.py$/.test(file.path))
|
|
42
|
+
.map((file) => file.path);
|
|
43
|
+
}
|
|
44
|
+
async function collectMiddleware(index, settingsFiles) {
|
|
45
|
+
const middlewares = new Set();
|
|
46
|
+
for (const filePath of settingsFiles) {
|
|
47
|
+
let source;
|
|
48
|
+
try {
|
|
49
|
+
source = await readFile(join(index.root, filePath), "utf-8");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const match = source.match(/MIDDLEWARE\s*=\s*\[([\s\S]*?)\]/);
|
|
55
|
+
if (!match?.[1])
|
|
56
|
+
continue;
|
|
57
|
+
const entries = match[1]
|
|
58
|
+
.split(",")
|
|
59
|
+
.map((value) => value.trim().replace(/['"]/g, ""))
|
|
60
|
+
.filter((value) => value.length > 0);
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
middlewares.add(entry);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return [...middlewares];
|
|
66
|
+
}
|
|
67
|
+
function classifyViewType(symbol) {
|
|
68
|
+
if (symbol.kind === "class")
|
|
69
|
+
return "class";
|
|
70
|
+
if (symbol.kind === "method")
|
|
71
|
+
return "method";
|
|
72
|
+
return "function";
|
|
73
|
+
}
|
|
74
|
+
function buildNotes(authGuards, csrfExempt, middleware) {
|
|
75
|
+
const notes = [];
|
|
76
|
+
const authMiddleware = middleware.some((entry) => entry.endsWith("AuthenticationMiddleware"));
|
|
77
|
+
const csrfMiddleware = middleware.some((entry) => entry.endsWith("CsrfViewMiddleware"));
|
|
78
|
+
if (authGuards.length === 0) {
|
|
79
|
+
if (authMiddleware) {
|
|
80
|
+
notes.push("No auth decorator or mixin detected; AuthenticationMiddleware alone does not restrict access.");
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
notes.push("No auth decorator or mixin detected, and AuthenticationMiddleware was not found in settings.");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (csrfExempt) {
|
|
87
|
+
notes.push("View is explicitly marked csrf_exempt.");
|
|
88
|
+
}
|
|
89
|
+
else if (!csrfMiddleware) {
|
|
90
|
+
notes.push("CsrfViewMiddleware was not found in settings, so CSRF protection may be absent globally.");
|
|
91
|
+
}
|
|
92
|
+
return notes;
|
|
93
|
+
}
|
|
94
|
+
function buildAssessment(symbol, parentSymbol, middleware, routePath) {
|
|
95
|
+
const decorators = [...(parentSymbol?.decorators ?? []), ...(symbol.decorators ?? [])];
|
|
96
|
+
const mixins = symbol.kind === "class"
|
|
97
|
+
? [...(symbol.extends ?? [])]
|
|
98
|
+
: [...(parentSymbol?.extends ?? [])];
|
|
99
|
+
const authGuards = collectAuthGuards(decorators, mixins);
|
|
100
|
+
const csrfExempt = hasDecorator(decorators, "csrf_exempt");
|
|
101
|
+
const authenticationMiddleware = middleware.some((entry) => entry.endsWith("AuthenticationMiddleware"));
|
|
102
|
+
const sessionMiddleware = middleware.some((entry) => entry.endsWith("SessionMiddleware"));
|
|
103
|
+
const securityMiddleware = middleware.some((entry) => entry.endsWith("SecurityMiddleware"));
|
|
104
|
+
const csrfProtected = !csrfExempt && middleware.some((entry) => entry.endsWith("CsrfViewMiddleware"));
|
|
105
|
+
const notes = buildNotes(authGuards, csrfExempt, middleware);
|
|
106
|
+
const assessment = {
|
|
107
|
+
symbol_name: symbol.name,
|
|
108
|
+
symbol_kind: symbol.kind,
|
|
109
|
+
file: symbol.file,
|
|
110
|
+
line: symbol.start_line,
|
|
111
|
+
view_type: classifyViewType(symbol),
|
|
112
|
+
decorators,
|
|
113
|
+
mixins,
|
|
114
|
+
auth_guards: authGuards,
|
|
115
|
+
csrf_exempt: csrfExempt,
|
|
116
|
+
effective_auth_required: authGuards.length > 0,
|
|
117
|
+
csrf_protected: csrfProtected,
|
|
118
|
+
authentication_middleware: authenticationMiddleware,
|
|
119
|
+
session_middleware: sessionMiddleware,
|
|
120
|
+
security_middleware: securityMiddleware,
|
|
121
|
+
notes,
|
|
122
|
+
confidence: parentSymbol || routePath ? "high" : "medium",
|
|
123
|
+
};
|
|
124
|
+
if (routePath !== undefined) {
|
|
125
|
+
assessment.route_path = routePath;
|
|
126
|
+
}
|
|
127
|
+
return assessment;
|
|
128
|
+
}
|
|
129
|
+
function dedupeAssessments(assessments) {
|
|
130
|
+
const seen = new Set();
|
|
131
|
+
const result = [];
|
|
132
|
+
for (const assessment of assessments) {
|
|
133
|
+
const key = `${assessment.file}:${assessment.line}:${assessment.symbol_name}`;
|
|
134
|
+
if (seen.has(key))
|
|
135
|
+
continue;
|
|
136
|
+
seen.add(key);
|
|
137
|
+
result.push(assessment);
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
function findParentSymbol(index, symbol) {
|
|
142
|
+
if (!symbol.parent)
|
|
143
|
+
return undefined;
|
|
144
|
+
return index.symbols.find((candidate) => candidate.id === symbol.parent);
|
|
145
|
+
}
|
|
146
|
+
async function resolveSymbolsFromPath(index, path) {
|
|
147
|
+
const trace = await traceRoute(index.repo, path);
|
|
148
|
+
if (!trace || typeof trace !== "object" || !("handlers" in trace))
|
|
149
|
+
return [];
|
|
150
|
+
const handlers = trace.handlers;
|
|
151
|
+
return handlers
|
|
152
|
+
.filter((handler) => handler.framework === "django")
|
|
153
|
+
.map((handler) => index.symbols.find((symbol) => symbol.file === handler.symbol.file &&
|
|
154
|
+
symbol.name === handler.symbol.name &&
|
|
155
|
+
symbol.start_line === handler.symbol.start_line))
|
|
156
|
+
.filter((symbol) => symbol !== undefined);
|
|
157
|
+
}
|
|
158
|
+
export async function effectiveDjangoViewSecurity(repo, options) {
|
|
159
|
+
const index = await getCodeIndex(repo);
|
|
160
|
+
if (!index)
|
|
161
|
+
throw new Error(`Repository "${repo}" not found.`);
|
|
162
|
+
if (!options.path && !options.symbol_name) {
|
|
163
|
+
throw new Error("Provide either path or symbol_name.");
|
|
164
|
+
}
|
|
165
|
+
const settingsFiles = getSettingsFiles(index, options.settings_file);
|
|
166
|
+
const middleware = await collectMiddleware(index, settingsFiles);
|
|
167
|
+
let symbols = [];
|
|
168
|
+
if (options.path) {
|
|
169
|
+
symbols = await resolveSymbolsFromPath(index, options.path);
|
|
170
|
+
}
|
|
171
|
+
else if (options.symbol_name) {
|
|
172
|
+
symbols = index.symbols.filter((symbol) => symbol.file.endsWith(".py")
|
|
173
|
+
&& symbol.name === options.symbol_name
|
|
174
|
+
&& (symbol.kind === "function" || symbol.kind === "class" || symbol.kind === "method")
|
|
175
|
+
&& (!options.file_pattern || symbol.file.includes(options.file_pattern)));
|
|
176
|
+
}
|
|
177
|
+
const assessments = dedupeAssessments(symbols.map((symbol) => buildAssessment(symbol, findParentSymbol(index, symbol), middleware, options.path)));
|
|
178
|
+
return {
|
|
179
|
+
assessments,
|
|
180
|
+
settings_files: settingsFiles,
|
|
181
|
+
middleware,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=django-view-security-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"django-view-security-tools.js","sourceRoot":"","sources":["../../src/tools/django-view-security-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface DependsCallSite {
|
|
2
|
+
/** Name of the dependency function (e.g. "get_db") */
|
|
3
|
+
name: string;
|
|
4
|
+
/** Raw Depends() expression, e.g. "Depends(get_db)" or "Security(oauth2_scheme, scopes=['admin'])" */
|
|
5
|
+
expression: string;
|
|
6
|
+
/** Whether this is a Security() call (auth dependency) */
|
|
7
|
+
is_security: boolean;
|
|
8
|
+
/** Security scopes if present */
|
|
9
|
+
scopes: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface DependsNode {
|
|
12
|
+
/** Dependency function name */
|
|
13
|
+
name: string;
|
|
14
|
+
/** File where the dependency is defined, if resolvable */
|
|
15
|
+
file?: string;
|
|
16
|
+
/** Line where the dependency is defined */
|
|
17
|
+
line?: number;
|
|
18
|
+
/** Sub-dependencies this dep itself uses */
|
|
19
|
+
depends_on: DependsNode[];
|
|
20
|
+
/** Is this a yield-based dependency (FastAPI cleanup pattern)? */
|
|
21
|
+
is_yield: boolean;
|
|
22
|
+
/** Is this a Security() dep? */
|
|
23
|
+
is_security: boolean;
|
|
24
|
+
/** Security scopes if present */
|
|
25
|
+
scopes: string[];
|
|
26
|
+
/** Depth in the tree (0 = directly attached to endpoint) */
|
|
27
|
+
depth: number;
|
|
28
|
+
}
|
|
29
|
+
export interface FastAPIEndpointDeps {
|
|
30
|
+
/** Endpoint function symbol name */
|
|
31
|
+
endpoint: string;
|
|
32
|
+
/** File path */
|
|
33
|
+
file: string;
|
|
34
|
+
/** Line */
|
|
35
|
+
line: number;
|
|
36
|
+
/** HTTP method and path, e.g. "GET /users/{id}" */
|
|
37
|
+
route?: string;
|
|
38
|
+
/** Full dependency tree rooted at this endpoint */
|
|
39
|
+
depends: DependsNode[];
|
|
40
|
+
/** All unique dep names used (flattened) */
|
|
41
|
+
all_deps: string[];
|
|
42
|
+
/** Are any Security() deps in the chain? */
|
|
43
|
+
has_auth: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface FastAPIDependsResult {
|
|
46
|
+
endpoints: FastAPIEndpointDeps[];
|
|
47
|
+
total_endpoints: number;
|
|
48
|
+
total_unique_deps: number;
|
|
49
|
+
endpoints_without_auth: string[];
|
|
50
|
+
shared_deps: Array<{
|
|
51
|
+
name: string;
|
|
52
|
+
used_by: number;
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Trace FastAPI Depends() chains for all endpoints in the repository.
|
|
57
|
+
*/
|
|
58
|
+
export declare function traceFastAPIDepends(repo: string, options?: {
|
|
59
|
+
file_pattern?: string;
|
|
60
|
+
endpoint?: string;
|
|
61
|
+
max_depth?: number;
|
|
62
|
+
}): Promise<FastAPIDependsResult>;
|
|
63
|
+
//# sourceMappingURL=fastapi-depends.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fastapi-depends.d.ts","sourceRoot":"","sources":["../../src/tools/fastapi-depends.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,sGAAsG;IACtG,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,WAAW,EAAE,OAAO,CAAC;IACrB,iCAAiC;IACjC,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,kEAAkE;IAClE,QAAQ,EAAE,OAAO,CAAC;IAClB,gCAAgC;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,iCAAiC;IACjC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW;IACX,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,4CAA4C;IAC5C,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,WAAW,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvD;AAWD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IACR,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,OAAO,CAAC,oBAAoB,CAAC,CA6F/B"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trace_fastapi_depends — FastAPI dependency injection graph.
|
|
3
|
+
*
|
|
4
|
+
* Traces `Depends(X)` and `Security(X)` chains recursively from route
|
|
5
|
+
* handlers through the dependency tree. Answers "what runs before my
|
|
6
|
+
* endpoint executes?" — auth, DB sessions, request parsing, etc.
|
|
7
|
+
*
|
|
8
|
+
* Detects:
|
|
9
|
+
* - Direct Depends() in function signatures
|
|
10
|
+
* - Nested Depends() (dep A uses dep B that uses dep C)
|
|
11
|
+
* - Security() (OAuth2, API key, scopes)
|
|
12
|
+
* - yield dependencies (resource cleanup via context managers)
|
|
13
|
+
* - global_dependencies in APIRouter/app
|
|
14
|
+
*
|
|
15
|
+
* Unique differentiator — no other MCP server traces FastAPI DI.
|
|
16
|
+
*/
|
|
17
|
+
import { getCodeIndex } from "./index-tools.js";
|
|
18
|
+
/** Match Depends(foo) or Depends(foo, use_cache=False) */
|
|
19
|
+
const DEPENDS_RE = /\b(Security|Depends)\s*\(\s*([\w.]+)(?:\s*,([^)]*))?\)/g;
|
|
20
|
+
/** Match scopes=["admin", "read"] inside a Security() call */
|
|
21
|
+
const SCOPES_RE = /scopes\s*=\s*\[([^\]]*)\]/;
|
|
22
|
+
/** Match FastAPI route decorator */
|
|
23
|
+
const ROUTE_DECORATOR_RE = /@\w+\.(get|post|put|delete|patch|options|head)\s*\(\s*['"]([^'"]*)['"]/;
|
|
24
|
+
const MAX_DEPTH = 5;
|
|
25
|
+
/**
|
|
26
|
+
* Trace FastAPI Depends() chains for all endpoints in the repository.
|
|
27
|
+
*/
|
|
28
|
+
export async function traceFastAPIDepends(repo, options) {
|
|
29
|
+
const index = await getCodeIndex(repo);
|
|
30
|
+
if (!index)
|
|
31
|
+
throw new Error(`Repository "${repo}" not found.`);
|
|
32
|
+
const filePattern = options?.file_pattern;
|
|
33
|
+
const endpointFilter = options?.endpoint;
|
|
34
|
+
const maxDepth = options?.max_depth ?? MAX_DEPTH;
|
|
35
|
+
// Build a lookup: function name → symbol (for resolving dep references)
|
|
36
|
+
const symbolByName = new Map();
|
|
37
|
+
for (const sym of index.symbols) {
|
|
38
|
+
if (!sym.file.endsWith(".py"))
|
|
39
|
+
continue;
|
|
40
|
+
if (sym.kind !== "function" && sym.kind !== "method")
|
|
41
|
+
continue;
|
|
42
|
+
if (!symbolByName.has(sym.name)) {
|
|
43
|
+
symbolByName.set(sym.name, sym);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Find FastAPI endpoints — functions with @app.get/@router.get etc.
|
|
47
|
+
const endpoints = [];
|
|
48
|
+
const sharedDeps = new Map();
|
|
49
|
+
for (const sym of index.symbols) {
|
|
50
|
+
if (!sym.file.endsWith(".py"))
|
|
51
|
+
continue;
|
|
52
|
+
if (sym.kind !== "function" && sym.kind !== "method")
|
|
53
|
+
continue;
|
|
54
|
+
if (filePattern && !sym.file.includes(filePattern))
|
|
55
|
+
continue;
|
|
56
|
+
if (endpointFilter && sym.name !== endpointFilter)
|
|
57
|
+
continue;
|
|
58
|
+
if (!sym.decorators || sym.decorators.length === 0)
|
|
59
|
+
continue;
|
|
60
|
+
// Check if any decorator matches FastAPI route pattern
|
|
61
|
+
let route;
|
|
62
|
+
for (const dec of sym.decorators) {
|
|
63
|
+
const m = dec.match(ROUTE_DECORATOR_RE);
|
|
64
|
+
if (m) {
|
|
65
|
+
route = `${m[1].toUpperCase()} ${m[2]}`;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!route)
|
|
70
|
+
continue;
|
|
71
|
+
// Extract direct Depends() from the function signature (stored in source)
|
|
72
|
+
const callSites = extractDependsFromSource(sym.source ?? "");
|
|
73
|
+
const directDeps = callSites.map((cs) => resolveDepNode(cs, symbolByName, new Set([sym.name]), 0, maxDepth));
|
|
74
|
+
// Flatten all_deps
|
|
75
|
+
const allDeps = new Set();
|
|
76
|
+
function collectDeps(nodes) {
|
|
77
|
+
for (const n of nodes) {
|
|
78
|
+
allDeps.add(n.name);
|
|
79
|
+
collectDeps(n.depends_on);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
collectDeps(directDeps);
|
|
83
|
+
// Check if any dep in the chain is Security
|
|
84
|
+
const hasAuth = hasSecurityInTree(directDeps);
|
|
85
|
+
// Track shared dep usage counts
|
|
86
|
+
for (const dep of allDeps) {
|
|
87
|
+
sharedDeps.set(dep, (sharedDeps.get(dep) ?? 0) + 1);
|
|
88
|
+
}
|
|
89
|
+
const ep = {
|
|
90
|
+
endpoint: sym.name,
|
|
91
|
+
file: sym.file,
|
|
92
|
+
line: sym.start_line,
|
|
93
|
+
depends: directDeps,
|
|
94
|
+
all_deps: [...allDeps].sort(),
|
|
95
|
+
has_auth: hasAuth,
|
|
96
|
+
};
|
|
97
|
+
if (route)
|
|
98
|
+
ep.route = route;
|
|
99
|
+
endpoints.push(ep);
|
|
100
|
+
}
|
|
101
|
+
// Shared deps: only include those used by 2+ endpoints
|
|
102
|
+
const sharedList = [...sharedDeps.entries()]
|
|
103
|
+
.filter(([, count]) => count >= 2)
|
|
104
|
+
.map(([name, count]) => ({ name, used_by: count }))
|
|
105
|
+
.sort((a, b) => b.used_by - a.used_by);
|
|
106
|
+
const endpointsWithoutAuth = endpoints
|
|
107
|
+
.filter((e) => !e.has_auth)
|
|
108
|
+
.map((e) => `${e.route ?? e.endpoint} (${e.file}:${e.line})`);
|
|
109
|
+
return {
|
|
110
|
+
endpoints,
|
|
111
|
+
total_endpoints: endpoints.length,
|
|
112
|
+
total_unique_deps: sharedDeps.size,
|
|
113
|
+
endpoints_without_auth: endpointsWithoutAuth,
|
|
114
|
+
shared_deps: sharedList,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Extract Depends() call sites from a function's source text.
|
|
119
|
+
* Returns call sites in the order they appear in the parameter list.
|
|
120
|
+
*/
|
|
121
|
+
function extractDependsFromSource(source) {
|
|
122
|
+
const sites = [];
|
|
123
|
+
DEPENDS_RE.lastIndex = 0;
|
|
124
|
+
let m;
|
|
125
|
+
while ((m = DEPENDS_RE.exec(source)) !== null) {
|
|
126
|
+
const kind = m[1];
|
|
127
|
+
const name = m[2];
|
|
128
|
+
const extra = m[3] ?? "";
|
|
129
|
+
const isSecurity = kind === "Security";
|
|
130
|
+
const scopes = [];
|
|
131
|
+
if (isSecurity) {
|
|
132
|
+
const scopeMatch = extra.match(SCOPES_RE);
|
|
133
|
+
if (scopeMatch) {
|
|
134
|
+
for (const s of scopeMatch[1].split(",")) {
|
|
135
|
+
const trimmed = s.trim().replace(/^['"]|['"]$/g, "");
|
|
136
|
+
if (trimmed)
|
|
137
|
+
scopes.push(trimmed);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
sites.push({
|
|
142
|
+
name,
|
|
143
|
+
expression: m[0],
|
|
144
|
+
is_security: isSecurity,
|
|
145
|
+
scopes,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return sites;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Resolve a dep call site to a DependsNode, recursively extracting sub-deps.
|
|
152
|
+
*/
|
|
153
|
+
function resolveDepNode(site, symbolByName, visited, depth, maxDepth) {
|
|
154
|
+
const sym = symbolByName.get(site.name);
|
|
155
|
+
const node = {
|
|
156
|
+
name: site.name,
|
|
157
|
+
depends_on: [],
|
|
158
|
+
is_yield: false,
|
|
159
|
+
is_security: site.is_security,
|
|
160
|
+
scopes: site.scopes,
|
|
161
|
+
depth,
|
|
162
|
+
};
|
|
163
|
+
if (sym) {
|
|
164
|
+
node.file = sym.file;
|
|
165
|
+
node.line = sym.start_line;
|
|
166
|
+
// Detect yield dependency (FastAPI resource cleanup pattern)
|
|
167
|
+
if (sym.source && /\byield\b/.test(sym.source)) {
|
|
168
|
+
node.is_yield = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Stop recursing if: too deep, already visited (cycle), or symbol missing
|
|
172
|
+
if (depth >= maxDepth || visited.has(site.name) || !sym) {
|
|
173
|
+
return node;
|
|
174
|
+
}
|
|
175
|
+
// Recurse: extract Depends() from this dep's source
|
|
176
|
+
const subVisited = new Set(visited);
|
|
177
|
+
subVisited.add(site.name);
|
|
178
|
+
const subSites = extractDependsFromSource(sym.source ?? "");
|
|
179
|
+
node.depends_on = subSites.map((sub) => resolveDepNode(sub, symbolByName, subVisited, depth + 1, maxDepth));
|
|
180
|
+
return node;
|
|
181
|
+
}
|
|
182
|
+
function hasSecurityInTree(nodes) {
|
|
183
|
+
for (const n of nodes) {
|
|
184
|
+
if (n.is_security)
|
|
185
|
+
return true;
|
|
186
|
+
if (hasSecurityInTree(n.depends_on))
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=fastapi-depends.js.map
|