@massu/core 1.5.4 → 1.5.5
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/dist/cli.js
CHANGED
|
@@ -15717,6 +15717,924 @@ var init_swift_swiftui = __esm({
|
|
|
15717
15717
|
}
|
|
15718
15718
|
});
|
|
15719
15719
|
|
|
15720
|
+
// src/detect/adapters/python-flask.ts
|
|
15721
|
+
function extractPrefixBase3(prefix3) {
|
|
15722
|
+
if (!prefix3.startsWith("/")) return null;
|
|
15723
|
+
const stripped = prefix3.replace(/^\/+/, "");
|
|
15724
|
+
const firstSeg = stripped.split("/")[0];
|
|
15725
|
+
if (!firstSeg) return null;
|
|
15726
|
+
return "/" + firstSeg;
|
|
15727
|
+
}
|
|
15728
|
+
var AUTH_DECORATOR_QUERY, BLUEPRINT_URL_PREFIX_QUERY, APP_FACTORY_QUERY, pythonFlaskAdapter;
|
|
15729
|
+
var init_python_flask = __esm({
|
|
15730
|
+
"src/detect/adapters/python-flask.ts"() {
|
|
15731
|
+
"use strict";
|
|
15732
|
+
init_tree_sitter();
|
|
15733
|
+
init_query_helpers();
|
|
15734
|
+
init_tree_sitter_loader();
|
|
15735
|
+
init_parse_guard();
|
|
15736
|
+
AUTH_DECORATOR_QUERY = `
|
|
15737
|
+
(decorator
|
|
15738
|
+
(identifier) @auth_decorator (#match? @auth_decorator "_required$"))
|
|
15739
|
+
`;
|
|
15740
|
+
BLUEPRINT_URL_PREFIX_QUERY = `
|
|
15741
|
+
(call
|
|
15742
|
+
function: (identifier) @_callee (#eq? @_callee "Blueprint")
|
|
15743
|
+
arguments: (argument_list
|
|
15744
|
+
(keyword_argument
|
|
15745
|
+
name: (identifier) @_kw (#eq? @_kw "url_prefix")
|
|
15746
|
+
value: (string) @url_prefix)))
|
|
15747
|
+
`;
|
|
15748
|
+
APP_FACTORY_QUERY = `
|
|
15749
|
+
(function_definition
|
|
15750
|
+
name: (identifier) @factory_name (#match? @factory_name "^create_")
|
|
15751
|
+
body: (block
|
|
15752
|
+
(expression_statement
|
|
15753
|
+
(assignment
|
|
15754
|
+
right: (call
|
|
15755
|
+
function: (identifier) @_flask_call (#eq? @_flask_call "Flask"))))))
|
|
15756
|
+
`;
|
|
15757
|
+
pythonFlaskAdapter = {
|
|
15758
|
+
id: "python-flask",
|
|
15759
|
+
languages: ["python"],
|
|
15760
|
+
matches(signals) {
|
|
15761
|
+
const pyToml = signals.pyprojectToml;
|
|
15762
|
+
if (pyToml?.__raw && /\bflask\b/i.test(pyToml.__raw)) return true;
|
|
15763
|
+
if (signals.presentDirs.has("app") && signals.presentFiles.has("app.py")) return true;
|
|
15764
|
+
if (signals.presentDirs.has("app") && signals.presentFiles.has("wsgi.py")) return true;
|
|
15765
|
+
return false;
|
|
15766
|
+
},
|
|
15767
|
+
async introspect(files, _rootDir) {
|
|
15768
|
+
if (files.length === 0) {
|
|
15769
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15770
|
+
}
|
|
15771
|
+
let language;
|
|
15772
|
+
try {
|
|
15773
|
+
language = await loadGrammar("python");
|
|
15774
|
+
} catch (e2) {
|
|
15775
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15776
|
+
}
|
|
15777
|
+
const parser = new Parser();
|
|
15778
|
+
parser.setLanguage(language);
|
|
15779
|
+
const authDecorators = /* @__PURE__ */ new Map();
|
|
15780
|
+
const urlPrefixes = /* @__PURE__ */ new Map();
|
|
15781
|
+
const appFactories = /* @__PURE__ */ new Map();
|
|
15782
|
+
try {
|
|
15783
|
+
for (const file of files) {
|
|
15784
|
+
const skip = isParsableSource(file.content, file.size);
|
|
15785
|
+
if (skip) {
|
|
15786
|
+
process.stderr.write(
|
|
15787
|
+
`[massu/ast] WARN: python-flask skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
15788
|
+
`
|
|
15789
|
+
);
|
|
15790
|
+
continue;
|
|
15791
|
+
}
|
|
15792
|
+
try {
|
|
15793
|
+
for (const hit of runQuery(parser, file.content, AUTH_DECORATOR_QUERY, "flask-auth-decorator", file.path)) {
|
|
15794
|
+
const name2 = hit.captures.auth_decorator;
|
|
15795
|
+
if (name2 && !authDecorators.has(name2)) {
|
|
15796
|
+
authDecorators.set(name2, { line: hit.line, file: file.path });
|
|
15797
|
+
}
|
|
15798
|
+
}
|
|
15799
|
+
for (const hit of runQuery(parser, file.content, BLUEPRINT_URL_PREFIX_QUERY, "flask-blueprint-url-prefix", file.path)) {
|
|
15800
|
+
const raw = hit.captures.url_prefix;
|
|
15801
|
+
if (!raw) continue;
|
|
15802
|
+
const literal = raw.replace(/^['"]/, "").replace(/['"]$/, "");
|
|
15803
|
+
const base = extractPrefixBase3(literal);
|
|
15804
|
+
if (base && !urlPrefixes.has(base)) {
|
|
15805
|
+
urlPrefixes.set(base, { line: hit.line, file: file.path });
|
|
15806
|
+
}
|
|
15807
|
+
}
|
|
15808
|
+
for (const hit of runQuery(parser, file.content, APP_FACTORY_QUERY, "flask-app-factory", file.path)) {
|
|
15809
|
+
const name2 = hit.captures.factory_name;
|
|
15810
|
+
if (name2 && !appFactories.has(name2)) {
|
|
15811
|
+
appFactories.set(name2, { line: hit.line, file: file.path });
|
|
15812
|
+
}
|
|
15813
|
+
}
|
|
15814
|
+
} catch (e2) {
|
|
15815
|
+
if (e2 instanceof InvalidQueryError) {
|
|
15816
|
+
throw e2;
|
|
15817
|
+
}
|
|
15818
|
+
continue;
|
|
15819
|
+
}
|
|
15820
|
+
}
|
|
15821
|
+
} finally {
|
|
15822
|
+
try {
|
|
15823
|
+
parser.delete();
|
|
15824
|
+
} catch {
|
|
15825
|
+
}
|
|
15826
|
+
}
|
|
15827
|
+
const conventions = {};
|
|
15828
|
+
const provenance = [];
|
|
15829
|
+
if (authDecorators.size === 1) {
|
|
15830
|
+
const [name2, { line, file }] = authDecorators.entries().next().value;
|
|
15831
|
+
conventions.auth_decorator = name2;
|
|
15832
|
+
provenance.push({ field: "auth_decorator", sourceFile: file, line, query: "flask-auth-decorator" });
|
|
15833
|
+
} else if (authDecorators.size >= 2) {
|
|
15834
|
+
const [name2, { line, file }] = authDecorators.entries().next().value;
|
|
15835
|
+
conventions.auth_decorator = name2;
|
|
15836
|
+
provenance.push({ field: "auth_decorator", sourceFile: file, line, query: "flask-auth-decorator" });
|
|
15837
|
+
}
|
|
15838
|
+
if (urlPrefixes.size >= 1) {
|
|
15839
|
+
const [base, { line, file }] = urlPrefixes.entries().next().value;
|
|
15840
|
+
conventions.blueprint_url_prefix = base;
|
|
15841
|
+
provenance.push({ field: "blueprint_url_prefix", sourceFile: file, line, query: "flask-blueprint-url-prefix" });
|
|
15842
|
+
}
|
|
15843
|
+
if (appFactories.size >= 1) {
|
|
15844
|
+
const [name2, { line, file }] = appFactories.entries().next().value;
|
|
15845
|
+
conventions.app_factory = name2;
|
|
15846
|
+
provenance.push({ field: "app_factory", sourceFile: file, line, query: "flask-app-factory" });
|
|
15847
|
+
}
|
|
15848
|
+
let confidence;
|
|
15849
|
+
if (Object.keys(conventions).length === 0) {
|
|
15850
|
+
confidence = "none";
|
|
15851
|
+
} else if (authDecorators.size === 1) {
|
|
15852
|
+
confidence = "high";
|
|
15853
|
+
} else if (authDecorators.size >= 2) {
|
|
15854
|
+
confidence = "low";
|
|
15855
|
+
} else {
|
|
15856
|
+
confidence = "medium";
|
|
15857
|
+
}
|
|
15858
|
+
return { conventions, provenance, confidence };
|
|
15859
|
+
}
|
|
15860
|
+
};
|
|
15861
|
+
}
|
|
15862
|
+
});
|
|
15863
|
+
|
|
15864
|
+
// src/detect/adapters/go-chi.ts
|
|
15865
|
+
function extractPrefixBase4(prefix3) {
|
|
15866
|
+
if (!prefix3.startsWith("/")) return null;
|
|
15867
|
+
const stripped = prefix3.replace(/^\/+/, "");
|
|
15868
|
+
const firstSeg = stripped.split("/")[0];
|
|
15869
|
+
if (!firstSeg) return null;
|
|
15870
|
+
return "/" + firstSeg;
|
|
15871
|
+
}
|
|
15872
|
+
var ROUTE_METHOD_QUERY, MOUNT_PREFIX_QUERY, MIDDLEWARE_USE_QUERY, goChiAdapter;
|
|
15873
|
+
var init_go_chi = __esm({
|
|
15874
|
+
"src/detect/adapters/go-chi.ts"() {
|
|
15875
|
+
"use strict";
|
|
15876
|
+
init_tree_sitter();
|
|
15877
|
+
init_query_helpers();
|
|
15878
|
+
init_tree_sitter_loader();
|
|
15879
|
+
init_parse_guard();
|
|
15880
|
+
ROUTE_METHOD_QUERY = `
|
|
15881
|
+
(call_expression
|
|
15882
|
+
function: (selector_expression
|
|
15883
|
+
field: (field_identifier) @method (#match? @method "^(Get|Post|Put|Delete|Patch|Head|Options|Connect|Trace)$"))
|
|
15884
|
+
arguments: (argument_list
|
|
15885
|
+
.
|
|
15886
|
+
(interpreted_string_literal) @route_path))
|
|
15887
|
+
`;
|
|
15888
|
+
MOUNT_PREFIX_QUERY = `
|
|
15889
|
+
(call_expression
|
|
15890
|
+
function: (selector_expression
|
|
15891
|
+
field: (field_identifier) @_field (#eq? @_field "Mount"))
|
|
15892
|
+
arguments: (argument_list
|
|
15893
|
+
.
|
|
15894
|
+
(interpreted_string_literal) @mount_path))
|
|
15895
|
+
`;
|
|
15896
|
+
MIDDLEWARE_USE_QUERY = `
|
|
15897
|
+
(call_expression
|
|
15898
|
+
function: (selector_expression
|
|
15899
|
+
field: (field_identifier) @_use (#eq? @_use "Use"))
|
|
15900
|
+
arguments: (argument_list
|
|
15901
|
+
.
|
|
15902
|
+
(selector_expression
|
|
15903
|
+
operand: (identifier) @_pkg (#eq? @_pkg "middleware")
|
|
15904
|
+
field: (field_identifier) @middleware_name)))
|
|
15905
|
+
`;
|
|
15906
|
+
goChiAdapter = {
|
|
15907
|
+
id: "go-chi",
|
|
15908
|
+
languages: ["go"],
|
|
15909
|
+
matches(signals) {
|
|
15910
|
+
if (!signals.goMod) return false;
|
|
15911
|
+
if (/github\.com\/go-chi\/chi/i.test(signals.goMod)) return true;
|
|
15912
|
+
return false;
|
|
15913
|
+
},
|
|
15914
|
+
async introspect(files, _rootDir) {
|
|
15915
|
+
if (files.length === 0) {
|
|
15916
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15917
|
+
}
|
|
15918
|
+
let language;
|
|
15919
|
+
try {
|
|
15920
|
+
language = await loadGrammar("go");
|
|
15921
|
+
} catch (e2) {
|
|
15922
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
15923
|
+
}
|
|
15924
|
+
const parser = new Parser();
|
|
15925
|
+
parser.setLanguage(language);
|
|
15926
|
+
const routeMethods = /* @__PURE__ */ new Map();
|
|
15927
|
+
const mountBases = /* @__PURE__ */ new Map();
|
|
15928
|
+
const middlewareNames = /* @__PURE__ */ new Map();
|
|
15929
|
+
try {
|
|
15930
|
+
for (const file of files) {
|
|
15931
|
+
const skip = isParsableSource(file.content, file.size);
|
|
15932
|
+
if (skip) {
|
|
15933
|
+
process.stderr.write(
|
|
15934
|
+
`[massu/ast] WARN: go-chi skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
15935
|
+
`
|
|
15936
|
+
);
|
|
15937
|
+
continue;
|
|
15938
|
+
}
|
|
15939
|
+
try {
|
|
15940
|
+
for (const hit of runQuery(parser, file.content, ROUTE_METHOD_QUERY, "chi-route-method", file.path)) {
|
|
15941
|
+
const method = hit.captures.method;
|
|
15942
|
+
if (method && !routeMethods.has(method)) {
|
|
15943
|
+
routeMethods.set(method, { line: hit.line, file: file.path });
|
|
15944
|
+
}
|
|
15945
|
+
}
|
|
15946
|
+
for (const hit of runQuery(parser, file.content, MOUNT_PREFIX_QUERY, "chi-mount-prefix", file.path)) {
|
|
15947
|
+
const raw = hit.captures.mount_path;
|
|
15948
|
+
if (!raw) continue;
|
|
15949
|
+
const literal = raw.replace(/^["`]/, "").replace(/["`]$/, "");
|
|
15950
|
+
const base = extractPrefixBase4(literal);
|
|
15951
|
+
if (base && !mountBases.has(base)) {
|
|
15952
|
+
mountBases.set(base, { line: hit.line, file: file.path });
|
|
15953
|
+
}
|
|
15954
|
+
}
|
|
15955
|
+
for (const hit of runQuery(parser, file.content, MIDDLEWARE_USE_QUERY, "chi-middleware-use", file.path)) {
|
|
15956
|
+
const name2 = hit.captures.middleware_name;
|
|
15957
|
+
if (name2 && !middlewareNames.has(name2)) {
|
|
15958
|
+
middlewareNames.set(name2, { line: hit.line, file: file.path });
|
|
15959
|
+
}
|
|
15960
|
+
}
|
|
15961
|
+
} catch (e2) {
|
|
15962
|
+
if (e2 instanceof InvalidQueryError) {
|
|
15963
|
+
throw e2;
|
|
15964
|
+
}
|
|
15965
|
+
continue;
|
|
15966
|
+
}
|
|
15967
|
+
}
|
|
15968
|
+
} finally {
|
|
15969
|
+
try {
|
|
15970
|
+
parser.delete();
|
|
15971
|
+
} catch {
|
|
15972
|
+
}
|
|
15973
|
+
}
|
|
15974
|
+
const conventions = {};
|
|
15975
|
+
const provenance = [];
|
|
15976
|
+
if (routeMethods.size === 1) {
|
|
15977
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
15978
|
+
conventions.route_method = name2;
|
|
15979
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "chi-route-method" });
|
|
15980
|
+
} else if (routeMethods.size >= 2) {
|
|
15981
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
15982
|
+
conventions.route_method = name2;
|
|
15983
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "chi-route-method" });
|
|
15984
|
+
}
|
|
15985
|
+
if (mountBases.size >= 1) {
|
|
15986
|
+
const [base, { line, file }] = mountBases.entries().next().value;
|
|
15987
|
+
conventions.mount_prefix_base = base;
|
|
15988
|
+
provenance.push({ field: "mount_prefix_base", sourceFile: file, line, query: "chi-mount-prefix" });
|
|
15989
|
+
}
|
|
15990
|
+
if (middlewareNames.size >= 1) {
|
|
15991
|
+
const [name2, { line, file }] = middlewareNames.entries().next().value;
|
|
15992
|
+
conventions.middleware_name = name2;
|
|
15993
|
+
provenance.push({ field: "middleware_name", sourceFile: file, line, query: "chi-middleware-use" });
|
|
15994
|
+
}
|
|
15995
|
+
let confidence;
|
|
15996
|
+
if (Object.keys(conventions).length === 0) {
|
|
15997
|
+
confidence = "none";
|
|
15998
|
+
} else if (routeMethods.size === 1) {
|
|
15999
|
+
confidence = "high";
|
|
16000
|
+
} else if (routeMethods.size >= 2) {
|
|
16001
|
+
confidence = "low";
|
|
16002
|
+
} else {
|
|
16003
|
+
confidence = "medium";
|
|
16004
|
+
}
|
|
16005
|
+
return { conventions, provenance, confidence };
|
|
16006
|
+
}
|
|
16007
|
+
};
|
|
16008
|
+
}
|
|
16009
|
+
});
|
|
16010
|
+
|
|
16011
|
+
// src/detect/adapters/rails.ts
|
|
16012
|
+
function extractRootController(target) {
|
|
16013
|
+
const idx = target.indexOf("#");
|
|
16014
|
+
if (idx <= 0) return null;
|
|
16015
|
+
const controller = target.slice(0, idx).trim();
|
|
16016
|
+
return controller || null;
|
|
16017
|
+
}
|
|
16018
|
+
var ROUTE_METHOD_QUERY2, NAMESPACE_QUERY, ROOT_ROUTE_QUERY, railsAdapter;
|
|
16019
|
+
var init_rails = __esm({
|
|
16020
|
+
"src/detect/adapters/rails.ts"() {
|
|
16021
|
+
"use strict";
|
|
16022
|
+
init_tree_sitter();
|
|
16023
|
+
init_query_helpers();
|
|
16024
|
+
init_tree_sitter_loader();
|
|
16025
|
+
init_parse_guard();
|
|
16026
|
+
ROUTE_METHOD_QUERY2 = `
|
|
16027
|
+
(call
|
|
16028
|
+
method: (identifier) @method (#match? @method "^(get|post|put|patch|delete|options|head)$")
|
|
16029
|
+
arguments: (argument_list
|
|
16030
|
+
.
|
|
16031
|
+
(string) @route_path))
|
|
16032
|
+
`;
|
|
16033
|
+
NAMESPACE_QUERY = `
|
|
16034
|
+
(call
|
|
16035
|
+
method: (identifier) @_method (#eq? @_method "namespace")
|
|
16036
|
+
arguments: (argument_list
|
|
16037
|
+
.
|
|
16038
|
+
[
|
|
16039
|
+
(simple_symbol) @namespace_symbol
|
|
16040
|
+
(string) @namespace_string
|
|
16041
|
+
]))
|
|
16042
|
+
`;
|
|
16043
|
+
ROOT_ROUTE_QUERY = `
|
|
16044
|
+
(call
|
|
16045
|
+
method: (identifier) @_method (#eq? @_method "root")
|
|
16046
|
+
arguments: (argument_list
|
|
16047
|
+
.
|
|
16048
|
+
(string) @root_target))
|
|
16049
|
+
|
|
16050
|
+
(call
|
|
16051
|
+
method: (identifier) @_method (#eq? @_method "root")
|
|
16052
|
+
arguments: (argument_list
|
|
16053
|
+
(pair
|
|
16054
|
+
key: (hash_key_symbol) @_key (#eq? @_key "to")
|
|
16055
|
+
value: (string) @root_target)))
|
|
16056
|
+
`;
|
|
16057
|
+
railsAdapter = {
|
|
16058
|
+
id: "rails",
|
|
16059
|
+
languages: ["ruby"],
|
|
16060
|
+
matches(signals) {
|
|
16061
|
+
if (!signals.gemfile) return false;
|
|
16062
|
+
return /^\s*gem\s+['"]rails['"]/im.test(signals.gemfile);
|
|
16063
|
+
},
|
|
16064
|
+
async introspect(files, _rootDir) {
|
|
16065
|
+
if (files.length === 0) {
|
|
16066
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16067
|
+
}
|
|
16068
|
+
let language;
|
|
16069
|
+
try {
|
|
16070
|
+
language = await loadGrammar("ruby");
|
|
16071
|
+
} catch (e2) {
|
|
16072
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16073
|
+
}
|
|
16074
|
+
const parser = new Parser();
|
|
16075
|
+
parser.setLanguage(language);
|
|
16076
|
+
const routeMethods = /* @__PURE__ */ new Map();
|
|
16077
|
+
const namespaces = /* @__PURE__ */ new Map();
|
|
16078
|
+
const rootControllers = /* @__PURE__ */ new Map();
|
|
16079
|
+
try {
|
|
16080
|
+
for (const file of files) {
|
|
16081
|
+
const skip = isParsableSource(file.content, file.size);
|
|
16082
|
+
if (skip) {
|
|
16083
|
+
process.stderr.write(
|
|
16084
|
+
`[massu/ast] WARN: rails skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
16085
|
+
`
|
|
16086
|
+
);
|
|
16087
|
+
continue;
|
|
16088
|
+
}
|
|
16089
|
+
try {
|
|
16090
|
+
for (const hit of runQuery(parser, file.content, ROUTE_METHOD_QUERY2, "rails-route-method", file.path)) {
|
|
16091
|
+
const method = hit.captures.method;
|
|
16092
|
+
if (method && !routeMethods.has(method)) {
|
|
16093
|
+
routeMethods.set(method, { line: hit.line, file: file.path });
|
|
16094
|
+
}
|
|
16095
|
+
}
|
|
16096
|
+
for (const hit of runQuery(parser, file.content, NAMESPACE_QUERY, "rails-namespace", file.path)) {
|
|
16097
|
+
const symbolRaw = hit.captures.namespace_symbol;
|
|
16098
|
+
const stringRaw = hit.captures.namespace_string;
|
|
16099
|
+
const name2 = symbolRaw ? symbolRaw.replace(/^:/, "") : stringRaw ? stringRaw.replace(/^['"]/, "").replace(/['"]$/, "") : null;
|
|
16100
|
+
if (!name2) continue;
|
|
16101
|
+
const path = "/" + name2;
|
|
16102
|
+
if (!namespaces.has(path)) {
|
|
16103
|
+
namespaces.set(path, { line: hit.line, file: file.path });
|
|
16104
|
+
}
|
|
16105
|
+
}
|
|
16106
|
+
for (const hit of runQuery(parser, file.content, ROOT_ROUTE_QUERY, "rails-root", file.path)) {
|
|
16107
|
+
const raw = hit.captures.root_target;
|
|
16108
|
+
if (!raw) continue;
|
|
16109
|
+
const literal = raw.replace(/^['"]/, "").replace(/['"]$/, "");
|
|
16110
|
+
const controller = extractRootController(literal);
|
|
16111
|
+
if (controller && !rootControllers.has(controller)) {
|
|
16112
|
+
rootControllers.set(controller, { line: hit.line, file: file.path });
|
|
16113
|
+
}
|
|
16114
|
+
}
|
|
16115
|
+
} catch (e2) {
|
|
16116
|
+
if (e2 instanceof InvalidQueryError) {
|
|
16117
|
+
throw e2;
|
|
16118
|
+
}
|
|
16119
|
+
continue;
|
|
16120
|
+
}
|
|
16121
|
+
}
|
|
16122
|
+
} finally {
|
|
16123
|
+
try {
|
|
16124
|
+
parser.delete();
|
|
16125
|
+
} catch {
|
|
16126
|
+
}
|
|
16127
|
+
}
|
|
16128
|
+
const conventions = {};
|
|
16129
|
+
const provenance = [];
|
|
16130
|
+
if (routeMethods.size === 1) {
|
|
16131
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16132
|
+
conventions.route_method = name2;
|
|
16133
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "rails-route-method" });
|
|
16134
|
+
} else if (routeMethods.size >= 2) {
|
|
16135
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16136
|
+
conventions.route_method = name2;
|
|
16137
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "rails-route-method" });
|
|
16138
|
+
}
|
|
16139
|
+
if (namespaces.size >= 1) {
|
|
16140
|
+
const [path, { line, file }] = namespaces.entries().next().value;
|
|
16141
|
+
conventions.api_namespace = path;
|
|
16142
|
+
provenance.push({ field: "api_namespace", sourceFile: file, line, query: "rails-namespace" });
|
|
16143
|
+
}
|
|
16144
|
+
if (rootControllers.size >= 1) {
|
|
16145
|
+
const [name2, { line, file }] = rootControllers.entries().next().value;
|
|
16146
|
+
conventions.root_controller = name2;
|
|
16147
|
+
provenance.push({ field: "root_controller", sourceFile: file, line, query: "rails-root" });
|
|
16148
|
+
}
|
|
16149
|
+
let confidence;
|
|
16150
|
+
if (Object.keys(conventions).length === 0) {
|
|
16151
|
+
confidence = "none";
|
|
16152
|
+
} else if (routeMethods.size === 1) {
|
|
16153
|
+
confidence = "high";
|
|
16154
|
+
} else if (routeMethods.size >= 2) {
|
|
16155
|
+
confidence = "low";
|
|
16156
|
+
} else {
|
|
16157
|
+
confidence = "medium";
|
|
16158
|
+
}
|
|
16159
|
+
return { conventions, provenance, confidence };
|
|
16160
|
+
}
|
|
16161
|
+
};
|
|
16162
|
+
}
|
|
16163
|
+
});
|
|
16164
|
+
|
|
16165
|
+
// src/detect/adapters/phoenix.ts
|
|
16166
|
+
function extractPrefixBase5(prefix3) {
|
|
16167
|
+
if (!prefix3.startsWith("/")) return null;
|
|
16168
|
+
const stripped = prefix3.replace(/^\/+/, "");
|
|
16169
|
+
const firstSeg = stripped.split("/")[0];
|
|
16170
|
+
if (!firstSeg) return null;
|
|
16171
|
+
return "/" + firstSeg;
|
|
16172
|
+
}
|
|
16173
|
+
var ROUTE_METHOD_QUERY3, SCOPE_PATH_QUERY, ROUTER_MODULE_QUERY, phoenixAdapter;
|
|
16174
|
+
var init_phoenix = __esm({
|
|
16175
|
+
"src/detect/adapters/phoenix.ts"() {
|
|
16176
|
+
"use strict";
|
|
16177
|
+
init_tree_sitter();
|
|
16178
|
+
init_query_helpers();
|
|
16179
|
+
init_tree_sitter_loader();
|
|
16180
|
+
init_parse_guard();
|
|
16181
|
+
ROUTE_METHOD_QUERY3 = `
|
|
16182
|
+
(call
|
|
16183
|
+
(identifier) @method (#match? @method "^(get|post|put|patch|delete|options|head)$")
|
|
16184
|
+
(arguments
|
|
16185
|
+
.
|
|
16186
|
+
(string) @route_path))
|
|
16187
|
+
`;
|
|
16188
|
+
SCOPE_PATH_QUERY = `
|
|
16189
|
+
(call
|
|
16190
|
+
(identifier) @_method (#eq? @_method "scope")
|
|
16191
|
+
(arguments
|
|
16192
|
+
.
|
|
16193
|
+
(string) @scope_path)
|
|
16194
|
+
(do_block))
|
|
16195
|
+
`;
|
|
16196
|
+
ROUTER_MODULE_QUERY = `
|
|
16197
|
+
(call
|
|
16198
|
+
(identifier) @_method (#eq? @_method "defmodule")
|
|
16199
|
+
(arguments
|
|
16200
|
+
.
|
|
16201
|
+
(alias) @module_name (#match? @module_name "Router$"))
|
|
16202
|
+
(do_block))
|
|
16203
|
+
`;
|
|
16204
|
+
phoenixAdapter = {
|
|
16205
|
+
id: "phoenix",
|
|
16206
|
+
languages: ["elixir"],
|
|
16207
|
+
matches(signals) {
|
|
16208
|
+
if (!signals.mixExs) return false;
|
|
16209
|
+
return /\{\s*:phoenix\b(?!_)/.test(signals.mixExs);
|
|
16210
|
+
},
|
|
16211
|
+
async introspect(files, _rootDir) {
|
|
16212
|
+
if (files.length === 0) {
|
|
16213
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16214
|
+
}
|
|
16215
|
+
let language;
|
|
16216
|
+
try {
|
|
16217
|
+
language = await loadGrammar("elixir");
|
|
16218
|
+
} catch (e2) {
|
|
16219
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16220
|
+
}
|
|
16221
|
+
const parser = new Parser();
|
|
16222
|
+
parser.setLanguage(language);
|
|
16223
|
+
const routeMethods = /* @__PURE__ */ new Map();
|
|
16224
|
+
const scopePaths = /* @__PURE__ */ new Map();
|
|
16225
|
+
const routerModules = /* @__PURE__ */ new Map();
|
|
16226
|
+
try {
|
|
16227
|
+
for (const file of files) {
|
|
16228
|
+
const skip = isParsableSource(file.content, file.size);
|
|
16229
|
+
if (skip) {
|
|
16230
|
+
process.stderr.write(
|
|
16231
|
+
`[massu/ast] WARN: phoenix skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
16232
|
+
`
|
|
16233
|
+
);
|
|
16234
|
+
continue;
|
|
16235
|
+
}
|
|
16236
|
+
try {
|
|
16237
|
+
for (const hit of runQuery(parser, file.content, ROUTE_METHOD_QUERY3, "phoenix-route-method", file.path)) {
|
|
16238
|
+
const method = hit.captures.method;
|
|
16239
|
+
if (method && !routeMethods.has(method)) {
|
|
16240
|
+
routeMethods.set(method, { line: hit.line, file: file.path });
|
|
16241
|
+
}
|
|
16242
|
+
}
|
|
16243
|
+
for (const hit of runQuery(parser, file.content, SCOPE_PATH_QUERY, "phoenix-scope-path", file.path)) {
|
|
16244
|
+
const raw = hit.captures.scope_path;
|
|
16245
|
+
if (!raw) continue;
|
|
16246
|
+
const literal = raw.replace(/^["']/, "").replace(/["']$/, "");
|
|
16247
|
+
const base = extractPrefixBase5(literal);
|
|
16248
|
+
if (base && !scopePaths.has(base)) {
|
|
16249
|
+
scopePaths.set(base, { line: hit.line, file: file.path });
|
|
16250
|
+
}
|
|
16251
|
+
}
|
|
16252
|
+
for (const hit of runQuery(parser, file.content, ROUTER_MODULE_QUERY, "phoenix-router-module", file.path)) {
|
|
16253
|
+
const name2 = hit.captures.module_name;
|
|
16254
|
+
if (name2 && !routerModules.has(name2)) {
|
|
16255
|
+
routerModules.set(name2, { line: hit.line, file: file.path });
|
|
16256
|
+
}
|
|
16257
|
+
}
|
|
16258
|
+
} catch (e2) {
|
|
16259
|
+
if (e2 instanceof InvalidQueryError) {
|
|
16260
|
+
throw e2;
|
|
16261
|
+
}
|
|
16262
|
+
continue;
|
|
16263
|
+
}
|
|
16264
|
+
}
|
|
16265
|
+
} finally {
|
|
16266
|
+
try {
|
|
16267
|
+
parser.delete();
|
|
16268
|
+
} catch {
|
|
16269
|
+
}
|
|
16270
|
+
}
|
|
16271
|
+
const conventions = {};
|
|
16272
|
+
const provenance = [];
|
|
16273
|
+
if (routeMethods.size === 1) {
|
|
16274
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16275
|
+
conventions.route_method = name2;
|
|
16276
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "phoenix-route-method" });
|
|
16277
|
+
} else if (routeMethods.size >= 2) {
|
|
16278
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16279
|
+
conventions.route_method = name2;
|
|
16280
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "phoenix-route-method" });
|
|
16281
|
+
}
|
|
16282
|
+
if (scopePaths.size >= 1) {
|
|
16283
|
+
const [base, { line, file }] = scopePaths.entries().next().value;
|
|
16284
|
+
conventions.scope_prefix_base = base;
|
|
16285
|
+
provenance.push({ field: "scope_prefix_base", sourceFile: file, line, query: "phoenix-scope-path" });
|
|
16286
|
+
}
|
|
16287
|
+
if (routerModules.size >= 1) {
|
|
16288
|
+
const [name2, { line, file }] = routerModules.entries().next().value;
|
|
16289
|
+
conventions.router_module = name2;
|
|
16290
|
+
provenance.push({ field: "router_module", sourceFile: file, line, query: "phoenix-router-module" });
|
|
16291
|
+
}
|
|
16292
|
+
let confidence;
|
|
16293
|
+
if (Object.keys(conventions).length === 0) {
|
|
16294
|
+
confidence = "none";
|
|
16295
|
+
} else if (routeMethods.size === 1) {
|
|
16296
|
+
confidence = "high";
|
|
16297
|
+
} else if (routeMethods.size >= 2) {
|
|
16298
|
+
confidence = "low";
|
|
16299
|
+
} else {
|
|
16300
|
+
confidence = "medium";
|
|
16301
|
+
}
|
|
16302
|
+
return { conventions, provenance, confidence };
|
|
16303
|
+
}
|
|
16304
|
+
};
|
|
16305
|
+
}
|
|
16306
|
+
});
|
|
16307
|
+
|
|
16308
|
+
// src/detect/adapters/aspnet.ts
|
|
16309
|
+
function extractPrefixBase6(prefix3) {
|
|
16310
|
+
const stripped = prefix3.replace(/^\/+/, "");
|
|
16311
|
+
const firstSeg = stripped.split("/")[0];
|
|
16312
|
+
if (!firstSeg) return null;
|
|
16313
|
+
return "/" + firstSeg;
|
|
16314
|
+
}
|
|
16315
|
+
var MAP_VERB_QUERY, HTTP_ATTR_QUERY, ROUTE_ATTR_QUERY, CONTROLLER_CLASS_QUERY, aspnetAdapter;
|
|
16316
|
+
var init_aspnet = __esm({
|
|
16317
|
+
"src/detect/adapters/aspnet.ts"() {
|
|
16318
|
+
"use strict";
|
|
16319
|
+
init_tree_sitter();
|
|
16320
|
+
init_query_helpers();
|
|
16321
|
+
init_tree_sitter_loader();
|
|
16322
|
+
init_parse_guard();
|
|
16323
|
+
MAP_VERB_QUERY = `
|
|
16324
|
+
(invocation_expression
|
|
16325
|
+
function: (member_access_expression
|
|
16326
|
+
name: (identifier) @method (#match? @method "^Map(Get|Post|Put|Patch|Delete|Head|Options)$"))
|
|
16327
|
+
arguments: (argument_list
|
|
16328
|
+
.
|
|
16329
|
+
(argument (string_literal) @route_path)))
|
|
16330
|
+
`;
|
|
16331
|
+
HTTP_ATTR_QUERY = `
|
|
16332
|
+
(attribute
|
|
16333
|
+
name: (identifier) @attr_name (#match? @attr_name "^Http(Get|Post|Put|Patch|Delete|Head|Options)$"))
|
|
16334
|
+
`;
|
|
16335
|
+
ROUTE_ATTR_QUERY = `
|
|
16336
|
+
(attribute
|
|
16337
|
+
name: (identifier) @_attr_name (#eq? @_attr_name "Route")
|
|
16338
|
+
(attribute_argument_list
|
|
16339
|
+
(attribute_argument (string_literal) @route_template)))
|
|
16340
|
+
`;
|
|
16341
|
+
CONTROLLER_CLASS_QUERY = `
|
|
16342
|
+
(class_declaration
|
|
16343
|
+
name: (identifier) @class_name (#match? @class_name "Controller$"))
|
|
16344
|
+
`;
|
|
16345
|
+
aspnetAdapter = {
|
|
16346
|
+
id: "aspnet",
|
|
16347
|
+
languages: ["csharp"],
|
|
16348
|
+
matches(signals) {
|
|
16349
|
+
if (!signals.csproj) return false;
|
|
16350
|
+
if (/Sdk\s*=\s*["']Microsoft\.NET\.Sdk\.Web["']/i.test(signals.csproj)) return true;
|
|
16351
|
+
if (/Microsoft\.AspNetCore\.App/i.test(signals.csproj)) return true;
|
|
16352
|
+
return false;
|
|
16353
|
+
},
|
|
16354
|
+
async introspect(files, _rootDir) {
|
|
16355
|
+
if (files.length === 0) {
|
|
16356
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16357
|
+
}
|
|
16358
|
+
let language;
|
|
16359
|
+
try {
|
|
16360
|
+
language = await loadGrammar("csharp");
|
|
16361
|
+
} catch (e2) {
|
|
16362
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16363
|
+
}
|
|
16364
|
+
const parser = new Parser();
|
|
16365
|
+
parser.setLanguage(language);
|
|
16366
|
+
const routeMethods = /* @__PURE__ */ new Map();
|
|
16367
|
+
const prefixBases = /* @__PURE__ */ new Map();
|
|
16368
|
+
const controllerClasses = /* @__PURE__ */ new Map();
|
|
16369
|
+
try {
|
|
16370
|
+
for (const file of files) {
|
|
16371
|
+
const skip = isParsableSource(file.content, file.size);
|
|
16372
|
+
if (skip) {
|
|
16373
|
+
process.stderr.write(
|
|
16374
|
+
`[massu/ast] WARN: aspnet skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
16375
|
+
`
|
|
16376
|
+
);
|
|
16377
|
+
continue;
|
|
16378
|
+
}
|
|
16379
|
+
try {
|
|
16380
|
+
for (const hit of runQuery(parser, file.content, MAP_VERB_QUERY, "aspnet-map-verb", file.path)) {
|
|
16381
|
+
const methodRaw = hit.captures.method;
|
|
16382
|
+
if (!methodRaw) continue;
|
|
16383
|
+
const verb = methodRaw.replace(/^Map/, "");
|
|
16384
|
+
if (!routeMethods.has(verb)) {
|
|
16385
|
+
routeMethods.set(verb, { line: hit.line, file: file.path });
|
|
16386
|
+
}
|
|
16387
|
+
const pathRaw = hit.captures.route_path;
|
|
16388
|
+
if (pathRaw) {
|
|
16389
|
+
const literal = pathRaw.replace(/^["']/, "").replace(/["']$/, "");
|
|
16390
|
+
const base = extractPrefixBase6(literal);
|
|
16391
|
+
if (base && !prefixBases.has(base)) {
|
|
16392
|
+
prefixBases.set(base, { line: hit.line, file: file.path });
|
|
16393
|
+
}
|
|
16394
|
+
}
|
|
16395
|
+
}
|
|
16396
|
+
for (const hit of runQuery(parser, file.content, HTTP_ATTR_QUERY, "aspnet-http-attr", file.path)) {
|
|
16397
|
+
const attrRaw = hit.captures.attr_name;
|
|
16398
|
+
if (!attrRaw) continue;
|
|
16399
|
+
const verb = attrRaw.replace(/^Http/, "");
|
|
16400
|
+
if (!routeMethods.has(verb)) {
|
|
16401
|
+
routeMethods.set(verb, { line: hit.line, file: file.path });
|
|
16402
|
+
}
|
|
16403
|
+
}
|
|
16404
|
+
for (const hit of runQuery(parser, file.content, ROUTE_ATTR_QUERY, "aspnet-route-attr", file.path)) {
|
|
16405
|
+
const tplRaw = hit.captures.route_template;
|
|
16406
|
+
if (!tplRaw) continue;
|
|
16407
|
+
const literal = tplRaw.replace(/^["']/, "").replace(/["']$/, "");
|
|
16408
|
+
const base = extractPrefixBase6(literal);
|
|
16409
|
+
if (base && !prefixBases.has(base)) {
|
|
16410
|
+
prefixBases.set(base, { line: hit.line, file: file.path });
|
|
16411
|
+
}
|
|
16412
|
+
}
|
|
16413
|
+
for (const hit of runQuery(parser, file.content, CONTROLLER_CLASS_QUERY, "aspnet-controller-class", file.path)) {
|
|
16414
|
+
const name2 = hit.captures.class_name;
|
|
16415
|
+
if (name2 && !controllerClasses.has(name2)) {
|
|
16416
|
+
controllerClasses.set(name2, { line: hit.line, file: file.path });
|
|
16417
|
+
}
|
|
16418
|
+
}
|
|
16419
|
+
} catch (e2) {
|
|
16420
|
+
if (e2 instanceof InvalidQueryError) {
|
|
16421
|
+
throw e2;
|
|
16422
|
+
}
|
|
16423
|
+
continue;
|
|
16424
|
+
}
|
|
16425
|
+
}
|
|
16426
|
+
} finally {
|
|
16427
|
+
try {
|
|
16428
|
+
parser.delete();
|
|
16429
|
+
} catch {
|
|
16430
|
+
}
|
|
16431
|
+
}
|
|
16432
|
+
const conventions = {};
|
|
16433
|
+
const provenance = [];
|
|
16434
|
+
if (routeMethods.size === 1) {
|
|
16435
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16436
|
+
conventions.route_method = name2;
|
|
16437
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "aspnet-map-verb" });
|
|
16438
|
+
} else if (routeMethods.size >= 2) {
|
|
16439
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16440
|
+
conventions.route_method = name2;
|
|
16441
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "aspnet-map-verb" });
|
|
16442
|
+
}
|
|
16443
|
+
if (prefixBases.size >= 1) {
|
|
16444
|
+
const [base, { line, file }] = prefixBases.entries().next().value;
|
|
16445
|
+
conventions.route_prefix_base = base;
|
|
16446
|
+
provenance.push({ field: "route_prefix_base", sourceFile: file, line, query: "aspnet-route-prefix" });
|
|
16447
|
+
}
|
|
16448
|
+
if (controllerClasses.size >= 1) {
|
|
16449
|
+
const [name2, { line, file }] = controllerClasses.entries().next().value;
|
|
16450
|
+
conventions.controller_class = name2;
|
|
16451
|
+
provenance.push({ field: "controller_class", sourceFile: file, line, query: "aspnet-controller-class" });
|
|
16452
|
+
}
|
|
16453
|
+
let confidence;
|
|
16454
|
+
if (Object.keys(conventions).length === 0) {
|
|
16455
|
+
confidence = "none";
|
|
16456
|
+
} else if (routeMethods.size === 1) {
|
|
16457
|
+
confidence = "high";
|
|
16458
|
+
} else if (routeMethods.size >= 2) {
|
|
16459
|
+
confidence = "low";
|
|
16460
|
+
} else {
|
|
16461
|
+
confidence = "medium";
|
|
16462
|
+
}
|
|
16463
|
+
return { conventions, provenance, confidence };
|
|
16464
|
+
}
|
|
16465
|
+
};
|
|
16466
|
+
}
|
|
16467
|
+
});
|
|
16468
|
+
|
|
16469
|
+
// src/detect/adapters/spring.ts
|
|
16470
|
+
function extractPrefixBase7(prefix3) {
|
|
16471
|
+
const stripped = prefix3.replace(/^\/+/, "");
|
|
16472
|
+
const firstSeg = stripped.split("/")[0];
|
|
16473
|
+
if (!firstSeg) return null;
|
|
16474
|
+
return "/" + firstSeg;
|
|
16475
|
+
}
|
|
16476
|
+
var HTTP_MAPPING_QUERY, HTTP_MAPPING_NO_ARGS_QUERY, REQUEST_MAPPING_QUERY, CONTROLLER_CLASS_QUERY2, springAdapter;
|
|
16477
|
+
var init_spring = __esm({
|
|
16478
|
+
"src/detect/adapters/spring.ts"() {
|
|
16479
|
+
"use strict";
|
|
16480
|
+
init_tree_sitter();
|
|
16481
|
+
init_query_helpers();
|
|
16482
|
+
init_tree_sitter_loader();
|
|
16483
|
+
init_parse_guard();
|
|
16484
|
+
HTTP_MAPPING_QUERY = `
|
|
16485
|
+
(annotation
|
|
16486
|
+
name: (identifier) @method (#match? @method "^(Get|Post|Put|Patch|Delete|Head|Options)Mapping$")
|
|
16487
|
+
arguments: (annotation_argument_list
|
|
16488
|
+
(string_literal) @route_path))
|
|
16489
|
+
`;
|
|
16490
|
+
HTTP_MAPPING_NO_ARGS_QUERY = `
|
|
16491
|
+
(marker_annotation
|
|
16492
|
+
name: (identifier) @method (#match? @method "^(Get|Post|Put|Patch|Delete|Head|Options)Mapping$"))
|
|
16493
|
+
`;
|
|
16494
|
+
REQUEST_MAPPING_QUERY = `
|
|
16495
|
+
(annotation
|
|
16496
|
+
name: (identifier) @_name (#eq? @_name "RequestMapping")
|
|
16497
|
+
arguments: (annotation_argument_list
|
|
16498
|
+
(string_literal) @route_template))
|
|
16499
|
+
`;
|
|
16500
|
+
CONTROLLER_CLASS_QUERY2 = `
|
|
16501
|
+
(class_declaration
|
|
16502
|
+
(modifiers
|
|
16503
|
+
(marker_annotation
|
|
16504
|
+
name: (identifier) @_anno (#match? @_anno "^(RestController|Controller)$")))
|
|
16505
|
+
name: (identifier) @class_name)
|
|
16506
|
+
|
|
16507
|
+
(class_declaration
|
|
16508
|
+
(modifiers
|
|
16509
|
+
(annotation
|
|
16510
|
+
name: (identifier) @_anno (#match? @_anno "^(RestController|Controller)$")))
|
|
16511
|
+
name: (identifier) @class_name)
|
|
16512
|
+
`;
|
|
16513
|
+
springAdapter = {
|
|
16514
|
+
id: "spring",
|
|
16515
|
+
languages: ["java"],
|
|
16516
|
+
matches(signals) {
|
|
16517
|
+
if (signals.pomXml && /\bspring-boot-starter[\w-]*\b/.test(signals.pomXml)) {
|
|
16518
|
+
return true;
|
|
16519
|
+
}
|
|
16520
|
+
if (signals.gradleBuild && /\bspring-boot-starter[\w-]*\b/.test(signals.gradleBuild)) {
|
|
16521
|
+
return true;
|
|
16522
|
+
}
|
|
16523
|
+
if (signals.pomXml && /\borg\.springframework\b/.test(signals.pomXml)) {
|
|
16524
|
+
return true;
|
|
16525
|
+
}
|
|
16526
|
+
if (signals.gradleBuild && /\borg\.springframework\b/.test(signals.gradleBuild)) {
|
|
16527
|
+
return true;
|
|
16528
|
+
}
|
|
16529
|
+
return false;
|
|
16530
|
+
},
|
|
16531
|
+
async introspect(files, _rootDir) {
|
|
16532
|
+
if (files.length === 0) {
|
|
16533
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16534
|
+
}
|
|
16535
|
+
let language;
|
|
16536
|
+
try {
|
|
16537
|
+
language = await loadGrammar("java");
|
|
16538
|
+
} catch (e2) {
|
|
16539
|
+
return { conventions: {}, provenance: [], confidence: "none" };
|
|
16540
|
+
}
|
|
16541
|
+
const parser = new Parser();
|
|
16542
|
+
parser.setLanguage(language);
|
|
16543
|
+
const routeMethods = /* @__PURE__ */ new Map();
|
|
16544
|
+
const prefixBases = /* @__PURE__ */ new Map();
|
|
16545
|
+
const controllerClasses = /* @__PURE__ */ new Map();
|
|
16546
|
+
try {
|
|
16547
|
+
for (const file of files) {
|
|
16548
|
+
const skip = isParsableSource(file.content, file.size);
|
|
16549
|
+
if (skip) {
|
|
16550
|
+
process.stderr.write(
|
|
16551
|
+
`[massu/ast] WARN: spring skipping ${file.path}: ${skip.reason} (${skip.detail}). Cap=${MAX_AST_FILE_BYTES}. (Phase 3.5 mitigation)
|
|
16552
|
+
`
|
|
16553
|
+
);
|
|
16554
|
+
continue;
|
|
16555
|
+
}
|
|
16556
|
+
try {
|
|
16557
|
+
for (const hit of runQuery(parser, file.content, HTTP_MAPPING_QUERY, "spring-http-mapping", file.path)) {
|
|
16558
|
+
const methodRaw = hit.captures.method;
|
|
16559
|
+
if (!methodRaw) continue;
|
|
16560
|
+
const verb = methodRaw.replace(/Mapping$/, "");
|
|
16561
|
+
if (!routeMethods.has(verb)) {
|
|
16562
|
+
routeMethods.set(verb, { line: hit.line, file: file.path });
|
|
16563
|
+
}
|
|
16564
|
+
}
|
|
16565
|
+
for (const hit of runQuery(parser, file.content, HTTP_MAPPING_NO_ARGS_QUERY, "spring-http-mapping-marker", file.path)) {
|
|
16566
|
+
const methodRaw = hit.captures.method;
|
|
16567
|
+
if (!methodRaw) continue;
|
|
16568
|
+
const verb = methodRaw.replace(/Mapping$/, "");
|
|
16569
|
+
if (!routeMethods.has(verb)) {
|
|
16570
|
+
routeMethods.set(verb, { line: hit.line, file: file.path });
|
|
16571
|
+
}
|
|
16572
|
+
}
|
|
16573
|
+
for (const hit of runQuery(parser, file.content, REQUEST_MAPPING_QUERY, "spring-request-mapping", file.path)) {
|
|
16574
|
+
const tplRaw = hit.captures.route_template;
|
|
16575
|
+
if (!tplRaw) continue;
|
|
16576
|
+
const literal = tplRaw.replace(/^["']/, "").replace(/["']$/, "");
|
|
16577
|
+
const base = extractPrefixBase7(literal);
|
|
16578
|
+
if (base && !prefixBases.has(base)) {
|
|
16579
|
+
prefixBases.set(base, { line: hit.line, file: file.path });
|
|
16580
|
+
}
|
|
16581
|
+
}
|
|
16582
|
+
for (const hit of runQuery(parser, file.content, CONTROLLER_CLASS_QUERY2, "spring-controller-class", file.path)) {
|
|
16583
|
+
const name2 = hit.captures.class_name;
|
|
16584
|
+
if (name2 && !controllerClasses.has(name2)) {
|
|
16585
|
+
controllerClasses.set(name2, { line: hit.line, file: file.path });
|
|
16586
|
+
}
|
|
16587
|
+
}
|
|
16588
|
+
} catch (e2) {
|
|
16589
|
+
if (e2 instanceof InvalidQueryError) {
|
|
16590
|
+
throw e2;
|
|
16591
|
+
}
|
|
16592
|
+
continue;
|
|
16593
|
+
}
|
|
16594
|
+
}
|
|
16595
|
+
} finally {
|
|
16596
|
+
try {
|
|
16597
|
+
parser.delete();
|
|
16598
|
+
} catch {
|
|
16599
|
+
}
|
|
16600
|
+
}
|
|
16601
|
+
const conventions = {};
|
|
16602
|
+
const provenance = [];
|
|
16603
|
+
if (routeMethods.size === 1) {
|
|
16604
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16605
|
+
conventions.route_method = name2;
|
|
16606
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "spring-http-mapping" });
|
|
16607
|
+
} else if (routeMethods.size >= 2) {
|
|
16608
|
+
const [name2, { line, file }] = routeMethods.entries().next().value;
|
|
16609
|
+
conventions.route_method = name2;
|
|
16610
|
+
provenance.push({ field: "route_method", sourceFile: file, line, query: "spring-http-mapping" });
|
|
16611
|
+
}
|
|
16612
|
+
if (prefixBases.size >= 1) {
|
|
16613
|
+
const [base, { line, file }] = prefixBases.entries().next().value;
|
|
16614
|
+
conventions.route_prefix_base = base;
|
|
16615
|
+
provenance.push({ field: "route_prefix_base", sourceFile: file, line, query: "spring-request-mapping" });
|
|
16616
|
+
}
|
|
16617
|
+
if (controllerClasses.size >= 1) {
|
|
16618
|
+
const [name2, { line, file }] = controllerClasses.entries().next().value;
|
|
16619
|
+
conventions.controller_class = name2;
|
|
16620
|
+
provenance.push({ field: "controller_class", sourceFile: file, line, query: "spring-controller-class" });
|
|
16621
|
+
}
|
|
16622
|
+
let confidence;
|
|
16623
|
+
if (Object.keys(conventions).length === 0) {
|
|
16624
|
+
confidence = "none";
|
|
16625
|
+
} else if (routeMethods.size === 1) {
|
|
16626
|
+
confidence = "high";
|
|
16627
|
+
} else if (routeMethods.size >= 2) {
|
|
16628
|
+
confidence = "low";
|
|
16629
|
+
} else {
|
|
16630
|
+
confidence = "medium";
|
|
16631
|
+
}
|
|
16632
|
+
return { conventions, provenance, confidence };
|
|
16633
|
+
}
|
|
16634
|
+
};
|
|
16635
|
+
}
|
|
16636
|
+
});
|
|
16637
|
+
|
|
15720
16638
|
// src/detect/adapters/file-sampler.ts
|
|
15721
16639
|
var file_sampler_exports = {};
|
|
15722
16640
|
__export(file_sampler_exports, {
|
|
@@ -15940,11 +16858,23 @@ var init_codebase_introspector = __esm({
|
|
|
15940
16858
|
init_python_django();
|
|
15941
16859
|
init_nextjs_trpc();
|
|
15942
16860
|
init_swift_swiftui();
|
|
16861
|
+
init_python_flask();
|
|
16862
|
+
init_go_chi();
|
|
16863
|
+
init_rails();
|
|
16864
|
+
init_phoenix();
|
|
16865
|
+
init_aspnet();
|
|
16866
|
+
init_spring();
|
|
15943
16867
|
FIRST_PARTY_ADAPTERS = [
|
|
15944
16868
|
pythonFastApiAdapter,
|
|
15945
16869
|
pythonDjangoAdapter,
|
|
16870
|
+
pythonFlaskAdapter,
|
|
15946
16871
|
nextjsTrpcAdapter,
|
|
15947
|
-
swiftSwiftUiAdapter
|
|
16872
|
+
swiftSwiftUiAdapter,
|
|
16873
|
+
goChiAdapter,
|
|
16874
|
+
railsAdapter,
|
|
16875
|
+
phoenixAdapter,
|
|
16876
|
+
aspnetAdapter,
|
|
16877
|
+
springAdapter
|
|
15948
16878
|
];
|
|
15949
16879
|
}
|
|
15950
16880
|
});
|
|
@@ -13309,6 +13309,24 @@ init_parse_guard();
|
|
|
13309
13309
|
// src/detect/adapters/swift-swiftui.ts
|
|
13310
13310
|
init_parse_guard();
|
|
13311
13311
|
|
|
13312
|
+
// src/detect/adapters/python-flask.ts
|
|
13313
|
+
init_parse_guard();
|
|
13314
|
+
|
|
13315
|
+
// src/detect/adapters/go-chi.ts
|
|
13316
|
+
init_parse_guard();
|
|
13317
|
+
|
|
13318
|
+
// src/detect/adapters/rails.ts
|
|
13319
|
+
init_parse_guard();
|
|
13320
|
+
|
|
13321
|
+
// src/detect/adapters/phoenix.ts
|
|
13322
|
+
init_parse_guard();
|
|
13323
|
+
|
|
13324
|
+
// src/detect/adapters/aspnet.ts
|
|
13325
|
+
init_parse_guard();
|
|
13326
|
+
|
|
13327
|
+
// src/detect/adapters/spring.ts
|
|
13328
|
+
init_parse_guard();
|
|
13329
|
+
|
|
13312
13330
|
// src/detect/codebase-introspector.ts
|
|
13313
13331
|
function introspect(detection, projectRoot) {
|
|
13314
13332
|
const out2 = {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massu/core",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI Engineering Governance MCP Server - Session memory, knowledge system, feature registry, code intelligence, rule enforcement, tiered tooling (12 free / 72 total), 55+ workflow commands, 11 agents, 20+ patterns",
|
|
6
6
|
"main": "src/server.ts",
|
|
@@ -48,6 +48,19 @@ import { pythonFastApiAdapter } from './adapters/python-fastapi.ts';
|
|
|
48
48
|
import { pythonDjangoAdapter } from './adapters/python-django.ts';
|
|
49
49
|
import { nextjsTrpcAdapter } from './adapters/nextjs-trpc.ts';
|
|
50
50
|
import { swiftSwiftUiAdapter } from './adapters/swift-swiftui.ts';
|
|
51
|
+
// Plan 1.5.4 R-011 discovery: Phase 7 adapters were committed but never
|
|
52
|
+
// added to FIRST_PARTY_ADAPTERS. The omission was masked pre-1.5.4 by
|
|
53
|
+
// sampleFiles=[] (every adapter returned 'none' anyway). Now that the
|
|
54
|
+
// sampler works, these 6 adapters MUST be in the dispatch list or
|
|
55
|
+
// `npx massu init` against a Phase 7 project produces no detected.<id>:
|
|
56
|
+
// block (verified via debug instrumentation 2026-05-08 against the
|
|
57
|
+
// 1.5.4 published bundle).
|
|
58
|
+
import { pythonFlaskAdapter } from './adapters/python-flask.ts';
|
|
59
|
+
import { goChiAdapter } from './adapters/go-chi.ts';
|
|
60
|
+
import { railsAdapter } from './adapters/rails.ts';
|
|
61
|
+
import { phoenixAdapter } from './adapters/phoenix.ts';
|
|
62
|
+
import { aspnetAdapter } from './adapters/aspnet.ts';
|
|
63
|
+
import { springAdapter } from './adapters/spring.ts';
|
|
51
64
|
import type { CodebaseAdapter, AdapterResolved } from './adapters/types.ts';
|
|
52
65
|
|
|
53
66
|
// ============================================================
|
|
@@ -75,8 +88,14 @@ export interface DetectedConventions {
|
|
|
75
88
|
const FIRST_PARTY_ADAPTERS: CodebaseAdapter[] = [
|
|
76
89
|
pythonFastApiAdapter,
|
|
77
90
|
pythonDjangoAdapter,
|
|
91
|
+
pythonFlaskAdapter,
|
|
78
92
|
nextjsTrpcAdapter,
|
|
79
93
|
swiftSwiftUiAdapter,
|
|
94
|
+
goChiAdapter,
|
|
95
|
+
railsAdapter,
|
|
96
|
+
phoenixAdapter,
|
|
97
|
+
aspnetAdapter,
|
|
98
|
+
springAdapter,
|
|
80
99
|
];
|
|
81
100
|
|
|
82
101
|
// ============================================================
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// AUTO-GENERATED by scripts/bundle-pubkey.mjs at 2026-05-08T20:
|
|
1
|
+
// AUTO-GENERATED by scripts/bundle-pubkey.mjs at 2026-05-08T20:50:59.749Z.
|
|
2
2
|
// Source pem: packages/core/security/registry-pubkey.pem
|
|
3
3
|
// RAW-bytes sha256: 3b6226d036c472e533110d11a7d0cd2773ce1d7d4f1003517d5bd69c5418ed4c
|
|
4
4
|
// DO NOT EDIT — regenerate via `node scripts/bundle-pubkey.mjs` or
|