candor-ts 0.5.10 → 0.5.12
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/package.json +1 -1
- package/scan.mjs +49 -0
package/package.json
CHANGED
package/scan.mjs
CHANGED
|
@@ -788,6 +788,27 @@ function visitCalls(node) {
|
|
|
788
788
|
if (t) rec.edges.add(t);
|
|
789
789
|
}
|
|
790
790
|
}
|
|
791
|
+
// `fn.call(thisArg, …)` / `fn.apply(thisArg, args)` INVOKE the receiver function reference, and
|
|
792
|
+
// `Reflect.apply(fn, …)` / `Reflect.construct(Ctor, …)` invoke their FIRST ARGUMENT. The resolved
|
|
793
|
+
// signature lands on the es-lib `CallableFunction.call/apply` / `Reflect.apply` member, so the
|
|
794
|
+
// function actually invoked (the receiver, or arg0) was never followed → the caller read
|
|
795
|
+
// silent-pure (HIGH: a common reflective-invoke shape). Edge to the referenced unit, mirroring the
|
|
796
|
+
// HOF-ref arm: a pure ref edges to a pure unit (no fabrication); a non-fn receiver/arg resolves to
|
|
797
|
+
// no minted unit (`nodeName` miss) and adds nothing; an unresolvable ref stays opaque/Unknown.
|
|
798
|
+
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
799
|
+
const m = node.expression.name.text;
|
|
800
|
+
const recv = node.expression.expression;
|
|
801
|
+
const recvText = recv.getText().replace(/\s+/g, "");
|
|
802
|
+
let invokedRef = null;
|
|
803
|
+
if ((m === "call" || m === "apply") && recvText !== "Reflect") invokedRef = recv;
|
|
804
|
+
else if (recvText === "Reflect" && (m === "apply" || m === "construct"))
|
|
805
|
+
invokedRef = (node.arguments ?? [])[0] ?? null;
|
|
806
|
+
if (invokedRef && (ts.isIdentifier(invokedRef) || ts.isPropertyAccessExpression(invokedRef))) {
|
|
807
|
+
const d2 = realDecl(checker.getSymbolAtLocation(invokedRef));
|
|
808
|
+
const t = d2 && nodeName.get(d2);
|
|
809
|
+
if (t) rec.edges.add(t);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
791
812
|
if (mod === "<local>") {
|
|
792
813
|
const targetName = nodeName.get(decl);
|
|
793
814
|
if (targetName) {
|
|
@@ -917,12 +938,35 @@ function visitCalls(node) {
|
|
|
917
938
|
// entropy were invisible).
|
|
918
939
|
const name = decl.name ? decl.name.getText() : "";
|
|
919
940
|
const parent = decl.parent && decl.parent.name ? decl.parent.name.getText() : "";
|
|
941
|
+
// `eval(code)` executes an arbitrary code string — it can perform ANY effect, so it is genuinely
|
|
942
|
+
// Unknown (the same posture as `new Function(s)`, which already reads Unknown via the no-decl
|
|
943
|
+
// path). The es-lib declares `eval` as a top-level ambient function (parent is the global/source
|
|
944
|
+
// file, no type name), and `globalThis.eval`/`window.eval`/`self.eval` all resolve to the same
|
|
945
|
+
// `eval` declaration — so keying on the resolved name catches every access form. Without this it
|
|
946
|
+
// resolved to a benign es-lib member and read SILENT-PURE (a code-execution sink reported pure).
|
|
947
|
+
if (name === "eval" && parent !== "Math" && parent !== "JSON") {
|
|
948
|
+
rec.direct.add("Unknown");
|
|
949
|
+
rec.why.add("call:eval");
|
|
950
|
+
}
|
|
920
951
|
if ((parent === "DateConstructor" && name === "now") || (parent === "Performance" && name === "now"))
|
|
921
952
|
rec.direct.add("Clock");
|
|
922
953
|
if (parent === "Math" && name === "random") rec.direct.add("Rand");
|
|
923
954
|
if (ts.isNewExpression(node) && (node.arguments ?? []).length === 0
|
|
924
955
|
&& checker.getTypeAtLocation(node.expression)?.symbol?.name === "DateConstructor")
|
|
925
956
|
rec.direct.add("Clock");
|
|
957
|
+
// Browser/runtime NETWORK globals declared in lib.dom — no importable module for the κ table to
|
|
958
|
+
// key on, so they read SILENT-PURE. `XMLHttpRequest.send`/`.open` issue the HTTP request; the
|
|
959
|
+
// `EventSource`/`WebSocket` constructors open a connection on construction. Net. (Found by a
|
|
960
|
+
// Net-deep sweep. The npm `ws` package is already κ-covered; this is the bare browser global.)
|
|
961
|
+
if (parent === "XMLHttpRequest" && (name === "send" || name === "open")) rec.direct.add("Net");
|
|
962
|
+
// `new EventSource(url)` / `new WebSocket(url)`: the constructor is declared on an anonymous
|
|
963
|
+
// `declare var` object type (symbol `__type`, no usable parent name), but reaching the es-lib
|
|
964
|
+
// branch already proves the ctor resolved to lib.dom (not a project class shadowing the name),
|
|
965
|
+
// so the constructed identifier is the real browser global.
|
|
966
|
+
if (ts.isNewExpression(node)) {
|
|
967
|
+
const ctorName = node.expression.getText();
|
|
968
|
+
if (ctorName === "EventSource" || ctorName === "WebSocket") rec.direct.add("Net");
|
|
969
|
+
}
|
|
926
970
|
} else {
|
|
927
971
|
// The member token κ matches: the resolved declaration's name, EXCEPT a `new X()` call,
|
|
928
972
|
// whose declaration is a Constructor (empty name) — synthesize "new" so a rule can exempt
|
|
@@ -1077,6 +1121,11 @@ function visitCalls(node) {
|
|
|
1077
1121
|
&& !(checker.getSymbolAtLocation(callee)?.declarations ?? [])
|
|
1078
1122
|
.some((d) => projectFiles.has(path.resolve(d.getSourceFile().fileName))))
|
|
1079
1123
|
geff = "Net";
|
|
1124
|
+
// the fully-qualified global fetch — `globalThis.fetch`/`window.fetch`/`self.fetch` — is a
|
|
1125
|
+
// PropertyAccess callee the bare-identifier guard above misses, so it read silent-pure. Mirror the
|
|
1126
|
+
// `eval` global-qualifier handling (a runtime global a project would not shadow).
|
|
1127
|
+
else if (ctext === "globalThis.fetch" || ctext === "window.fetch" || ctext === "self.fetch")
|
|
1128
|
+
geff = "Net";
|
|
1080
1129
|
if (geff) {
|
|
1081
1130
|
const owner = enclosing(node);
|
|
1082
1131
|
if (owner) fns.get(owner).direct.add(geff);
|