@obfuscan/core 0.1.0

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.
Files changed (131) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +224 -0
  3. package/dist/allowlist.d.ts +25 -0
  4. package/dist/allowlist.d.ts.map +1 -0
  5. package/dist/allowlist.js +138 -0
  6. package/dist/allowlist.js.map +1 -0
  7. package/dist/detectors/bidi-control.d.ts +14 -0
  8. package/dist/detectors/bidi-control.d.ts.map +1 -0
  9. package/dist/detectors/bidi-control.js +67 -0
  10. package/dist/detectors/bidi-control.js.map +1 -0
  11. package/dist/detectors/cargo-build-rs-network.d.ts +12 -0
  12. package/dist/detectors/cargo-build-rs-network.d.ts.map +1 -0
  13. package/dist/detectors/cargo-build-rs-network.js +54 -0
  14. package/dist/detectors/cargo-build-rs-network.js.map +1 -0
  15. package/dist/detectors/decode-then-exec.d.ts +20 -0
  16. package/dist/detectors/decode-then-exec.d.ts.map +1 -0
  17. package/dist/detectors/decode-then-exec.js +189 -0
  18. package/dist/detectors/decode-then-exec.js.map +1 -0
  19. package/dist/detectors/deserializer-untrusted.d.ts +15 -0
  20. package/dist/detectors/deserializer-untrusted.d.ts.map +1 -0
  21. package/dist/detectors/deserializer-untrusted.js +99 -0
  22. package/dist/detectors/deserializer-untrusted.js.map +1 -0
  23. package/dist/detectors/dockerfile-curl-pipe-shell.d.ts +10 -0
  24. package/dist/detectors/dockerfile-curl-pipe-shell.d.ts.map +1 -0
  25. package/dist/detectors/dockerfile-curl-pipe-shell.js +42 -0
  26. package/dist/detectors/dockerfile-curl-pipe-shell.js.map +1 -0
  27. package/dist/detectors/dynamic-exec-non-literal.d.ts +17 -0
  28. package/dist/detectors/dynamic-exec-non-literal.d.ts.map +1 -0
  29. package/dist/detectors/dynamic-exec-non-literal.js +104 -0
  30. package/dist/detectors/dynamic-exec-non-literal.js.map +1 -0
  31. package/dist/detectors/encoded-array-fingerprint.d.ts +11 -0
  32. package/dist/detectors/encoded-array-fingerprint.d.ts.map +1 -0
  33. package/dist/detectors/encoded-array-fingerprint.js +60 -0
  34. package/dist/detectors/encoded-array-fingerprint.js.map +1 -0
  35. package/dist/detectors/gha-curl-pipe-shell.d.ts +11 -0
  36. package/dist/detectors/gha-curl-pipe-shell.d.ts.map +1 -0
  37. package/dist/detectors/gha-curl-pipe-shell.js +42 -0
  38. package/dist/detectors/gha-curl-pipe-shell.js.map +1 -0
  39. package/dist/detectors/high-entropy-literal.d.ts +19 -0
  40. package/dist/detectors/high-entropy-literal.d.ts.map +1 -0
  41. package/dist/detectors/high-entropy-literal.js +90 -0
  42. package/dist/detectors/high-entropy-literal.js.map +1 -0
  43. package/dist/detectors/homoglyph-identifier.d.ts +16 -0
  44. package/dist/detectors/homoglyph-identifier.d.ts.map +1 -0
  45. package/dist/detectors/homoglyph-identifier.js +76 -0
  46. package/dist/detectors/homoglyph-identifier.js.map +1 -0
  47. package/dist/detectors/index.d.ts +31 -0
  48. package/dist/detectors/index.d.ts.map +1 -0
  49. package/dist/detectors/index.js +60 -0
  50. package/dist/detectors/index.js.map +1 -0
  51. package/dist/detectors/library-load-non-literal.d.ts +10 -0
  52. package/dist/detectors/library-load-non-literal.d.ts.map +1 -0
  53. package/dist/detectors/library-load-non-literal.js +72 -0
  54. package/dist/detectors/library-load-non-literal.js.map +1 -0
  55. package/dist/detectors/long-line.d.ts +12 -0
  56. package/dist/detectors/long-line.d.ts.map +1 -0
  57. package/dist/detectors/long-line.js +53 -0
  58. package/dist/detectors/long-line.js.map +1 -0
  59. package/dist/detectors/manifest-install-script.d.ts +54 -0
  60. package/dist/detectors/manifest-install-script.d.ts.map +1 -0
  61. package/dist/detectors/manifest-install-script.js +272 -0
  62. package/dist/detectors/manifest-install-script.js.map +1 -0
  63. package/dist/detectors/network-then-exec.d.ts +17 -0
  64. package/dist/detectors/network-then-exec.d.ts.map +1 -0
  65. package/dist/detectors/network-then-exec.js +140 -0
  66. package/dist/detectors/network-then-exec.js.map +1 -0
  67. package/dist/detectors/perl-makefile-side-effect.d.ts +17 -0
  68. package/dist/detectors/perl-makefile-side-effect.d.ts.map +1 -0
  69. package/dist/detectors/perl-makefile-side-effect.js +72 -0
  70. package/dist/detectors/perl-makefile-side-effect.js.map +1 -0
  71. package/dist/detectors/python-setup-side-effect.d.ts +10 -0
  72. package/dist/detectors/python-setup-side-effect.d.ts.map +1 -0
  73. package/dist/detectors/python-setup-side-effect.js +87 -0
  74. package/dist/detectors/python-setup-side-effect.js.map +1 -0
  75. package/dist/detectors/shell-untrusted-input.d.ts +10 -0
  76. package/dist/detectors/shell-untrusted-input.d.ts.map +1 -0
  77. package/dist/detectors/shell-untrusted-input.js +76 -0
  78. package/dist/detectors/shell-untrusted-input.js.map +1 -0
  79. package/dist/detectors/string-array-decoder.d.ts +15 -0
  80. package/dist/detectors/string-array-decoder.d.ts.map +1 -0
  81. package/dist/detectors/string-array-decoder.js +70 -0
  82. package/dist/detectors/string-array-decoder.js.map +1 -0
  83. package/dist/detectors/suspicious-io-cluster.d.ts +11 -0
  84. package/dist/detectors/suspicious-io-cluster.d.ts.map +1 -0
  85. package/dist/detectors/suspicious-io-cluster.js +86 -0
  86. package/dist/detectors/suspicious-io-cluster.js.map +1 -0
  87. package/dist/diff.d.ts +23 -0
  88. package/dist/diff.d.ts.map +1 -0
  89. package/dist/diff.js +144 -0
  90. package/dist/diff.js.map +1 -0
  91. package/dist/directives.d.ts +33 -0
  92. package/dist/directives.d.ts.map +1 -0
  93. package/dist/directives.js +60 -0
  94. package/dist/directives.js.map +1 -0
  95. package/dist/errors.d.ts +19 -0
  96. package/dist/errors.d.ts.map +1 -0
  97. package/dist/errors.js +16 -0
  98. package/dist/errors.js.map +1 -0
  99. package/dist/grammar/query.d.ts +44 -0
  100. package/dist/grammar/query.d.ts.map +1 -0
  101. package/dist/grammar/query.js +24 -0
  102. package/dist/grammar/query.js.map +1 -0
  103. package/dist/index.d.ts +101 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +106 -0
  106. package/dist/index.js.map +1 -0
  107. package/dist/internal/patterns.d.ts +48 -0
  108. package/dist/internal/patterns.d.ts.map +1 -0
  109. package/dist/internal/patterns.js +95 -0
  110. package/dist/internal/patterns.js.map +1 -0
  111. package/dist/internal/text.d.ts +14 -0
  112. package/dist/internal/text.d.ts.map +1 -0
  113. package/dist/internal/text.js +20 -0
  114. package/dist/internal/text.js.map +1 -0
  115. package/dist/rules.d.ts +25 -0
  116. package/dist/rules.d.ts.map +1 -0
  117. package/dist/rules.js +195 -0
  118. package/dist/rules.js.map +1 -0
  119. package/dist/scan.d.ts +26 -0
  120. package/dist/scan.d.ts.map +1 -0
  121. package/dist/scan.js +287 -0
  122. package/dist/scan.js.map +1 -0
  123. package/dist/types.d.ts +215 -0
  124. package/dist/types.d.ts.map +1 -0
  125. package/dist/types.js +8 -0
  126. package/dist/types.js.map +1 -0
  127. package/dist/version.d.ts +10 -0
  128. package/dist/version.d.ts.map +1 -0
  129. package/dist/version.js +50 -0
  130. package/dist/version.js.map +1 -0
  131. package/package.json +74 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * obf.suspicious-io-cluster.<lang> — Layer B.
3
+ *
4
+ * Fires when a single file *both* reads from a known-secrets path
5
+ * (~/.npmrc, ~/.aws/credentials, ~/.ssh/id_*, etc.) *and* makes an
6
+ * outbound network call. The cluster is the signal — either alone is
7
+ * usually benign.
8
+ */
9
+ import { lineAtOffset, MAX_FINDINGS_PER_DETECTOR, MAX_SOURCE_BYTES, namedCallAlternation, } from "../internal/patterns.js";
10
+ import { truncateSnippet } from "../internal/text.js";
11
+ // Path fragments that strongly indicate a secrets read.
12
+ const SECRET_PATH_RE = /(\.npmrc|\.aws[\/\\]credentials|\.aws[\/\\]config|\.ssh[\/\\]id_[a-z0-9_]+|\.docker[\/\\]config|\.gitconfig|\.netrc|GITHUB_TOKEN|NPM_TOKEN|AWS_ACCESS_KEY)/g;
13
+ const cache = new WeakMap();
14
+ function compile(config) {
15
+ const cached = cache.get(config);
16
+ if (cached)
17
+ return cached;
18
+ const net = config.network_io ?? [];
19
+ const sec = config.secrets_io ?? [];
20
+ const shell = config.shell_exec ?? [];
21
+ const compiled = {
22
+ network: net.length ? new RegExp(`(?:${namedCallAlternation(net)})\\s*\\(`, "g") : null,
23
+ secretsIo: sec.length ? new RegExp(`(?:${namedCallAlternation(sec)})\\s*\\(`, "g") : null,
24
+ shellExec: shell.length ? new RegExp(`(?:${namedCallAlternation(shell)})\\s*\\(`, "g") : null,
25
+ };
26
+ cache.set(config, compiled);
27
+ return compiled;
28
+ }
29
+ export const suspiciousIoCluster = {
30
+ id: "obf.suspicious-io-cluster",
31
+ docsUrl: "https://github.com/bytebardorg/obfuscan/blob/main/docs/detectors.md#obfsuspicious-io-cluster",
32
+ applies(ctx) {
33
+ return (ctx.config !== null &&
34
+ ctx.source.length > 0 &&
35
+ ctx.source.length < MAX_SOURCE_BYTES);
36
+ },
37
+ run(ctx) {
38
+ if (!ctx.config)
39
+ return [];
40
+ const cfg = ctx.config;
41
+ const src = ctx.source;
42
+ const { network, shellExec } = compile(cfg);
43
+ if (!network)
44
+ return [];
45
+ // 1. Find at least one secret-path reference.
46
+ const secretPathMatch = SECRET_PATH_RE.exec(src);
47
+ SECRET_PATH_RE.lastIndex = 0;
48
+ const hasSecretPath = !!secretPathMatch;
49
+ // 2. Find at least one network call.
50
+ const netRe = new RegExp(network.source, network.flags);
51
+ const netMatch = netRe.exec(src);
52
+ if (!netMatch)
53
+ return [];
54
+ // Secondary cluster: shell + network in the same file. This catches
55
+ // setup.py and similar install-time droppers that fetch and execute.
56
+ const shellMatch = shellExec ? new RegExp(shellExec.source, shellExec.flags).exec(src) : null;
57
+ if (!hasSecretPath && !shellMatch)
58
+ return [];
59
+ const findings = [];
60
+ if (findings.length >= MAX_FINDINGS_PER_DETECTOR)
61
+ return findings;
62
+ // Anchor the finding on whichever appears earlier — gives the user a
63
+ // single jump-to location for the cluster.
64
+ const offset = hasSecretPath
65
+ ? Math.min(secretPathMatch.index, netMatch.index)
66
+ : Math.min(shellMatch.index, netMatch.index);
67
+ findings.push({
68
+ ruleId: `obf.suspicious-io-cluster.${cfg.id}`,
69
+ severity: "warn",
70
+ score: 8,
71
+ file: ctx.path,
72
+ line: lineAtOffset(src, offset),
73
+ snippet: truncateSnippet(src.slice(offset, Math.min(src.length, offset + 200))),
74
+ reason: `File reads from a known secrets location AND makes a network call. ` +
75
+ `This is the data-exfil cluster shape that defines supply-chain malware.`,
76
+ evidence: {
77
+ language: cfg.id,
78
+ secretMarker: secretPathMatch?.[0] ?? null,
79
+ shellCall: shellMatch?.[0] ?? null,
80
+ networkCall: netMatch[0],
81
+ },
82
+ });
83
+ return findings;
84
+ },
85
+ };
86
+ //# sourceMappingURL=suspicious-io-cluster.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suspicious-io-cluster.js","sourceRoot":"","sources":["../../src/detectors/suspicious-io-cluster.ts"],"names":[],"mappings":"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"}
package/dist/diff.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Unified-diff parser → DiffFile[].
3
+ *
4
+ * Pure, dependency-free, line-based parser. Handles:
5
+ * - added / modified / deleted file headers (`new file mode`, `deleted file mode`, `/dev/null`)
6
+ * - hunk headers `@@ -a,b +c,d @@`
7
+ * - paths normalized to forward slashes (no Windows backslashes leak through)
8
+ *
9
+ * `addedRanges` is a list of inclusive 1-based [startLine, endLine] tuples
10
+ * pointing into the *post-image* (the file as it exists after the diff is
11
+ * applied), covering only the lines marked with `+` in each hunk.
12
+ */
13
+ export interface DiffFile {
14
+ /** Workspace-relative POSIX path. */
15
+ path: string;
16
+ status: "added" | "modified" | "deleted";
17
+ /** 1-based inclusive line ranges of added lines in the post-image. Empty for deleted files. */
18
+ addedRanges: ReadonlyArray<readonly [number, number]>;
19
+ }
20
+ export declare function parseDiffToFiles(diff: string): DiffFile[];
21
+ /** True if `line` (1-based) falls within any of the given inclusive ranges. */
22
+ export declare function lineInRanges(line: number, ranges: ReadonlyArray<readonly [number, number]>): boolean;
23
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,QAAQ;IACvB,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;IACzC,+FAA+F;IAC/F,WAAW,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACvD;AAUD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAmHzD;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAC/C,OAAO,CAKT"}
package/dist/diff.js ADDED
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Unified-diff parser → DiffFile[].
3
+ *
4
+ * Pure, dependency-free, line-based parser. Handles:
5
+ * - added / modified / deleted file headers (`new file mode`, `deleted file mode`, `/dev/null`)
6
+ * - hunk headers `@@ -a,b +c,d @@`
7
+ * - paths normalized to forward slashes (no Windows backslashes leak through)
8
+ *
9
+ * `addedRanges` is a list of inclusive 1-based [startLine, endLine] tuples
10
+ * pointing into the *post-image* (the file as it exists after the diff is
11
+ * applied), covering only the lines marked with `+` in each hunk.
12
+ */
13
+ const FILE_HEADER = /^diff --git a\/(.+?) b\/(.+?)$/;
14
+ const HUNK_HEADER = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/;
15
+ export function parseDiffToFiles(diff) {
16
+ if (!diff || diff.length === 0)
17
+ return [];
18
+ const out = [];
19
+ const lines = diff.split(/\r?\n/);
20
+ let i = 0;
21
+ while (i < lines.length) {
22
+ const line = lines[i] ?? "";
23
+ const header = FILE_HEADER.exec(line);
24
+ if (!header) {
25
+ i++;
26
+ continue;
27
+ }
28
+ // Default to the b-side path; overridden below if /dev/null appears.
29
+ let path = (header[2] ?? "").replace(/\\/g, "/");
30
+ let status = "modified";
31
+ const addedRanges = [];
32
+ i++;
33
+ // Parse meta lines until we hit the first hunk or the next file header.
34
+ while (i < lines.length) {
35
+ const meta = lines[i] ?? "";
36
+ if (FILE_HEADER.test(meta))
37
+ break;
38
+ if (meta.startsWith("@@"))
39
+ break;
40
+ if (meta.startsWith("new file mode"))
41
+ status = "added";
42
+ else if (meta.startsWith("deleted file mode"))
43
+ status = "deleted";
44
+ else if (meta.startsWith("--- ")) {
45
+ // No-op; b-side path drives reporting.
46
+ }
47
+ else if (meta.startsWith("+++ ")) {
48
+ const rhs = meta.slice(4).trim();
49
+ if (rhs === "/dev/null")
50
+ status = "deleted";
51
+ else if (rhs.startsWith("b/"))
52
+ path = rhs.slice(2).replace(/\\/g, "/");
53
+ else if (rhs !== "/dev/null")
54
+ path = rhs.replace(/\\/g, "/");
55
+ }
56
+ i++;
57
+ }
58
+ // Parse hunks.
59
+ while (i < lines.length) {
60
+ const hunkLine = lines[i] ?? "";
61
+ if (FILE_HEADER.test(hunkLine))
62
+ break;
63
+ if (!hunkLine.startsWith("@@")) {
64
+ i++;
65
+ continue;
66
+ }
67
+ const m = HUNK_HEADER.exec(hunkLine);
68
+ if (!m) {
69
+ i++;
70
+ continue;
71
+ }
72
+ const hunk = {
73
+ postStart: parseInt(m[1] ?? "0", 10) || 0,
74
+ };
75
+ i++;
76
+ // Walk hunk body. Lines starting with "+" advance the post-image cursor
77
+ // and contribute to addedRanges. " " lines advance both. "-" lines do
78
+ // not advance the post cursor. "" is ignored.
79
+ let postCursor = hunk.postStart;
80
+ let runStart = null;
81
+ while (i < lines.length) {
82
+ const body = lines[i] ?? "";
83
+ if (FILE_HEADER.test(body) || body.startsWith("@@"))
84
+ break;
85
+ if (body.startsWith("+")) {
86
+ if (runStart === null)
87
+ runStart = postCursor;
88
+ postCursor++;
89
+ i++;
90
+ }
91
+ else if (body.startsWith(" ")) {
92
+ if (runStart !== null) {
93
+ addedRanges.push([runStart, postCursor - 1]);
94
+ runStart = null;
95
+ }
96
+ postCursor++;
97
+ i++;
98
+ }
99
+ else if (body.startsWith("-")) {
100
+ if (runStart !== null) {
101
+ addedRanges.push([runStart, postCursor - 1]);
102
+ runStart = null;
103
+ }
104
+ i++;
105
+ }
106
+ else if (body.startsWith("\\")) {
107
+ // "" — skip.
108
+ i++;
109
+ }
110
+ else if (body === "") {
111
+ // Blank line: treat as context unless it's the trailing blank from split.
112
+ if (i + 1 >= lines.length) {
113
+ i++;
114
+ break;
115
+ }
116
+ if (runStart !== null) {
117
+ addedRanges.push([runStart, postCursor - 1]);
118
+ runStart = null;
119
+ }
120
+ postCursor++;
121
+ i++;
122
+ }
123
+ else {
124
+ break;
125
+ }
126
+ }
127
+ if (runStart !== null)
128
+ addedRanges.push([runStart, postCursor - 1]);
129
+ }
130
+ if (status !== "deleted") {
131
+ out.push({ path, status, addedRanges });
132
+ }
133
+ }
134
+ return out;
135
+ }
136
+ /** True if `line` (1-based) falls within any of the given inclusive ranges. */
137
+ export function lineInRanges(line, ranges) {
138
+ for (const [start, end] of ranges) {
139
+ if (line >= start && line <= end)
140
+ return true;
141
+ }
142
+ return false;
143
+ }
144
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * In-source suppression directives.
3
+ *
4
+ * Three forms recognized, mirroring eslint's syntax. Comment style is
5
+ * autodetected — anything between `#`, `//`, `/* ... *\/`, `--`, `;`, or `'`
6
+ * is treated as a comment context.
7
+ *
8
+ * // obfuscan-disable-next-line obf.high-entropy-literal
9
+ * const k = "...";
10
+ *
11
+ * const k = "..."; // obfuscan-disable-line obf.high-entropy-literal
12
+ *
13
+ * /* obfuscan-disable-line obf.foo, obf.bar *\/
14
+ *
15
+ * Multiple rule ids may be comma-separated. Omitting the ids disables ALL
16
+ * detectors for that line — discouraged, but supported.
17
+ *
18
+ * The aggregator calls `extractDisableDirectives(source)` once per file and
19
+ * filters findings whose `(line, ruleId)` matches.
20
+ */
21
+ export interface DisableDirective {
22
+ /** 1-based line number that this directive suppresses. */
23
+ line: number;
24
+ /** Empty array means "all rules". */
25
+ ruleIds: readonly string[];
26
+ }
27
+ export declare function extractDisableDirectives(source: string): DisableDirective[];
28
+ /**
29
+ * True if `(line, ruleId)` is suppressed by any directive in `directives`.
30
+ * A directive with an empty ruleIds array suppresses every rule on its line.
31
+ */
32
+ export declare function isSuppressedByDirectives(line: number, ruleId: string, directives: readonly DisableDirective[]): boolean;
33
+ //# sourceMappingURL=directives.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directives.d.ts","sourceRoot":"","sources":["../src/directives.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AAKD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAsB3E;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,SAAS,gBAAgB,EAAE,GACtC,OAAO,CAST"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * In-source suppression directives.
3
+ *
4
+ * Three forms recognized, mirroring eslint's syntax. Comment style is
5
+ * autodetected — anything between `#`, `//`, `/* ... *\/`, `--`, `;`, or `'`
6
+ * is treated as a comment context.
7
+ *
8
+ * // obfuscan-disable-next-line obf.high-entropy-literal
9
+ * const k = "...";
10
+ *
11
+ * const k = "..."; // obfuscan-disable-line obf.high-entropy-literal
12
+ *
13
+ * /* obfuscan-disable-line obf.foo, obf.bar *\/
14
+ *
15
+ * Multiple rule ids may be comma-separated. Omitting the ids disables ALL
16
+ * detectors for that line — discouraged, but supported.
17
+ *
18
+ * The aggregator calls `extractDisableDirectives(source)` once per file and
19
+ * filters findings whose `(line, ruleId)` matches.
20
+ */
21
+ const DIRECTIVE_RE = /obfuscan-disable-(next-line|line)\b\s*([A-Za-z0-9_.,\- *]*)/g;
22
+ export function extractDisableDirectives(source) {
23
+ const out = [];
24
+ if (!source)
25
+ return out;
26
+ const lines = source.split("\n");
27
+ for (let i = 0; i < lines.length; i++) {
28
+ const line = lines[i] ?? "";
29
+ DIRECTIVE_RE.lastIndex = 0;
30
+ let m;
31
+ while ((m = DIRECTIVE_RE.exec(line)) !== null) {
32
+ const kind = m[1];
33
+ const idsRaw = (m[2] ?? "").trim();
34
+ const ruleIds = idsRaw
35
+ .split(/[\s,]+/)
36
+ .map(s => s.trim())
37
+ .filter(s => s.length > 0 && /^[A-Za-z0-9_.\-]+$/.test(s));
38
+ const targetLine = kind === "next-line" ? i + 2 : i + 1;
39
+ out.push({ line: targetLine, ruleIds });
40
+ }
41
+ }
42
+ return out;
43
+ }
44
+ /**
45
+ * True if `(line, ruleId)` is suppressed by any directive in `directives`.
46
+ * A directive with an empty ruleIds array suppresses every rule on its line.
47
+ */
48
+ export function isSuppressedByDirectives(line, ruleId, directives) {
49
+ for (const d of directives) {
50
+ if (d.line !== line)
51
+ continue;
52
+ if (d.ruleIds.length === 0)
53
+ return true;
54
+ if (d.ruleIds.some(id => ruleId === id || ruleId.startsWith(id + "."))) {
55
+ return true;
56
+ }
57
+ }
58
+ return false;
59
+ }
60
+ //# sourceMappingURL=directives.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directives.js","sourceRoot":"","sources":["../src/directives.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,MAAM,YAAY,GAChB,8DAA8D,CAAC;AAEjE,MAAM,UAAU,wBAAwB,CAAC,MAAc;IACrD,MAAM,GAAG,GAAuB,EAAE,CAAC;IACnC,IAAI,CAAC,MAAM;QAAE,OAAO,GAAG,CAAC;IACxB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAEjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5B,YAAY,CAAC,SAAS,GAAG,CAAC,CAAC;QAC3B,IAAI,CAAyB,CAAC;QAC9B,OAAO,CAAC,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YAC9C,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAClB,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,MAAM;iBACnB,KAAK,CAAC,QAAQ,CAAC;iBACf,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBAClB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAE7D,MAAM,UAAU,GAAG,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACxD,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CACtC,IAAY,EACZ,MAAc,EACd,UAAuC;IAEvC,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI;YAAE,SAAS;QAC9B,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACxC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,KAAK,EAAE,IAAI,MAAM,CAAC,UAAU,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;YACvE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Error classes — hoisted to a separate module to avoid circular imports
3
+ * between rules.ts, scan.ts, and index.ts.
4
+ */
5
+ export declare class InvalidScanInputError extends Error {
6
+ readonly name = "InvalidScanInputError";
7
+ }
8
+ export declare class InvalidRuleSetError extends Error {
9
+ readonly details: ReadonlyArray<{
10
+ file: string;
11
+ problem: string;
12
+ }>;
13
+ readonly name = "InvalidRuleSetError";
14
+ constructor(message: string, details: ReadonlyArray<{
15
+ file: string;
16
+ problem: string;
17
+ }>);
18
+ }
19
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,SAAkB,IAAI,2BAA2B;CAClD;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAI1C,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAHpE,SAAkB,IAAI,yBAAyB;gBAE7C,OAAO,EAAE,MAAM,EACN,OAAO,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAIrE"}
package/dist/errors.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Error classes — hoisted to a separate module to avoid circular imports
3
+ * between rules.ts, scan.ts, and index.ts.
4
+ */
5
+ export class InvalidScanInputError extends Error {
6
+ name = "InvalidScanInputError";
7
+ }
8
+ export class InvalidRuleSetError extends Error {
9
+ details;
10
+ name = "InvalidRuleSetError";
11
+ constructor(message, details) {
12
+ super(message);
13
+ this.details = details;
14
+ }
15
+ }
16
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC5B,IAAI,GAAG,uBAAuB,CAAC;CAClD;AAED,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAIjC;IAHO,IAAI,GAAG,qBAAqB,CAAC;IAC/C,YACE,OAAe,EACN,OAAyD;QAElE,KAAK,CAAC,OAAO,CAAC,CAAC;QAFN,YAAO,GAAP,OAAO,CAAkD;IAGpE,CAAC;CACF"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Tree-sitter query runner — internal facade.
3
+ *
4
+ * STATUS: stub. The real implementation will:
5
+ * 1. Resolve the per-grammar query file `queries/per-grammar/<lang>.scm`
6
+ * 2. Concatenate it with the shared template `queries/shared/<name>.scm`
7
+ * 3. Compile via `web-tree-sitter` Query, cached per (grammar, name)
8
+ * 4. Run against the parsed tree and yield captures
9
+ *
10
+ * Detectors depend only on the `QueryMatch` / `QueryNode` shapes below, so
11
+ * the engine implementation can evolve without rewriting detectors.
12
+ */
13
+ import type { GrammarHandle } from "../types.js";
14
+ /** A captured node from a tree-sitter query. */
15
+ export interface QueryNode {
16
+ /** Source text of the node. */
17
+ readonly text: string;
18
+ /** 0-based byte offset of the node start in the source. */
19
+ readonly startIndex: number;
20
+ /** 0-based byte offset of the node end (exclusive). */
21
+ readonly endIndex: number;
22
+ /** 0-based row/column of node start. */
23
+ readonly startPosition: {
24
+ readonly row: number;
25
+ readonly column: number;
26
+ };
27
+ /** 0-based row/column of node end. */
28
+ readonly endPosition: {
29
+ readonly row: number;
30
+ readonly column: number;
31
+ };
32
+ }
33
+ /** A single match from a query — a map from capture name to node. */
34
+ export interface QueryMatch {
35
+ readonly captures: ReadonlyMap<string, QueryNode>;
36
+ }
37
+ /**
38
+ * Run a named query against a parsed tree and return all matches.
39
+ *
40
+ * Throws if the query name is unknown for this grammar — detectors should
41
+ * catch and treat as "this language hasn't been ported to this query yet".
42
+ */
43
+ export declare function runQuery(_grammar: GrammarHandle, _tree: unknown, _queryName: string): readonly QueryMatch[];
44
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/grammar/query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,gDAAgD;AAChD,MAAM,WAAW,SAAS;IACxB,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,QAAQ,CAAC,aAAa,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,sCAAsC;IACtC,QAAQ,CAAC,WAAW,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACzE;AAED,qEAAqE;AACrE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACnD;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,OAAO,EACd,UAAU,EAAE,MAAM,GACjB,SAAS,UAAU,EAAE,CAIvB"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Tree-sitter query runner — internal facade.
3
+ *
4
+ * STATUS: stub. The real implementation will:
5
+ * 1. Resolve the per-grammar query file `queries/per-grammar/<lang>.scm`
6
+ * 2. Concatenate it with the shared template `queries/shared/<name>.scm`
7
+ * 3. Compile via `web-tree-sitter` Query, cached per (grammar, name)
8
+ * 4. Run against the parsed tree and yield captures
9
+ *
10
+ * Detectors depend only on the `QueryMatch` / `QueryNode` shapes below, so
11
+ * the engine implementation can evolve without rewriting detectors.
12
+ */
13
+ /**
14
+ * Run a named query against a parsed tree and return all matches.
15
+ *
16
+ * Throws if the query name is unknown for this grammar — detectors should
17
+ * catch and treat as "this language hasn't been ported to this query yet".
18
+ */
19
+ export function runQuery(_grammar, _tree, _queryName) {
20
+ // Reference stub: returns no matches. Replace with a real implementation
21
+ // that compiles `.scm` files and walks the tree.
22
+ return [];
23
+ }
24
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/grammar/query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAuBH;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CACtB,QAAuB,EACvB,KAAc,EACd,UAAkB;IAElB,yEAAyE;IACzE,iDAAiD;IACjD,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @obfuscan/core — public entry point.
3
+ *
4
+ * Stable API surface. Anything not re-exported here is internal and may
5
+ * change without a major version bump.
6
+ *
7
+ * ## Quick start
8
+ *
9
+ * ```ts
10
+ * import { scan } from "@obfuscan/core";
11
+ * import * as fs from "node:fs/promises";
12
+ *
13
+ * const result = await scan(
14
+ * { diff: await fs.readFile("pr.diff", "utf8") },
15
+ * { fileResolver: (p) => fs.readFile(p, "utf8") },
16
+ * );
17
+ *
18
+ * for (const f of result.findings) {
19
+ * console.log(`${f.severity.toUpperCase()} ${f.file}:${f.line} ${f.reason}`);
20
+ * }
21
+ * ```
22
+ *
23
+ * ## Custom rules
24
+ *
25
+ * ```ts
26
+ * import { scan, loadRuleSet } from "@obfuscan/core";
27
+ *
28
+ * const rules = await loadRuleSet({
29
+ * languageDir: "./my-rules/languages",
30
+ * queryDir: "./my-rules/queries",
31
+ * });
32
+ *
33
+ * const result = await scan({ dir: "./src" }, { fileResolver, rules });
34
+ * ```
35
+ *
36
+ * ## Custom detectors
37
+ *
38
+ * ```ts
39
+ * import { scan, defaultDetectors, Detector } from "@obfuscan/core";
40
+ *
41
+ * const myDetector: Detector = {
42
+ * id: "my-org.no-fetch-in-tests",
43
+ * applies: (ctx) => /\.test\./.test(ctx.path),
44
+ * run: (ctx) => /* ... *\/ [],
45
+ * };
46
+ *
47
+ * const result = await scan(input, {
48
+ * fileResolver,
49
+ * detectors: [...defaultDetectors(), myDetector],
50
+ * });
51
+ * ```
52
+ */
53
+ export { scan } from "./scan.js";
54
+ export type { Severity, Finding, LanguageConfig, RuleSet, GrammarHandle, PathAllowlistEntry, SnippetAllowlistEntry, Allowlist, ScanInput, ScanOptions, ScanProgress, ScanResult, FileResolver, SymbolResolver, Logger, Detector, FileContext, } from "./types.js";
55
+ /**
56
+ * Load a custom rule set from disk. Use this to point obfuscan at a fork of
57
+ * `@obfuscan/rules` or at an internal extension. If you only need the
58
+ * defaults, omit `rules` from `ScanOptions` and the built-in pack is used.
59
+ */
60
+ export { loadRuleSet, defaultRuleSet } from "./rules.js";
61
+ /**
62
+ * The shipped detector set. Use this as a base when you want to add or
63
+ * filter detectors:
64
+ *
65
+ * ```ts
66
+ * const dets = defaultDetectors().filter(d => d.id !== "obf.high-entropy-literal");
67
+ * ```
68
+ */
69
+ export { defaultDetectors } from "./detectors/index.js";
70
+ /**
71
+ * Read/write `.obfuscan/allowlist.json`. The CLI uses these directly.
72
+ * Programmatic users typically pass an inline `allowlist` to `scan()`
73
+ * instead of touching disk.
74
+ */
75
+ export { loadAllowlist, saveAllowlist, hashSnippet, matchesAllowlist, } from "./allowlist.js";
76
+ /**
77
+ * Parse a unified diff into the shape `scan()` consumes internally.
78
+ * Exposed for hosts (e.g. a Git client) that already have a structured diff
79
+ * representation and want to skip re-serializing.
80
+ */
81
+ export { parseDiffToFiles } from "./diff.js";
82
+ export type { DiffFile } from "./diff.js";
83
+ /**
84
+ * Parses `// obfuscan-disable-next-line <ruleId>` and similar comment
85
+ * directives. Returns the set of (line, ruleId) pairs to suppress.
86
+ * Used internally by the aggregator; exposed for tooling that wants to
87
+ * surface them in a UI.
88
+ */
89
+ export { extractDisableDirectives } from "./directives.js";
90
+ export type { DisableDirective } from "./directives.js";
91
+ /** SemVer of the engine. Replaced at build time via `__ENGINE_VERSION__`. */
92
+ export { ENGINE_VERSION } from "./version.js";
93
+ /**
94
+ * `InvalidScanInputError` — thrown when `ScanInput` is missing all of
95
+ * `diff`, `paths`, `dir`, or specifies more than one.
96
+ *
97
+ * `InvalidRuleSetError` — thrown when a custom rule set fails validation.
98
+ * Includes a `details` array describing each problem.
99
+ */
100
+ export { InvalidScanInputError, InvalidRuleSetError } from "./errors.js";
101
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAIH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC,YAAY,EAEV,QAAQ,EACR,OAAO,EAEP,cAAc,EACd,OAAO,EACP,aAAa,EACb,kBAAkB,EAClB,qBAAqB,EACrB,SAAS,EAET,SAAS,EACT,WAAW,EACX,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,cAAc,EACd,MAAM,EAEN,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AAIpB;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAIzD;;;;;;;GAOG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAIxD;;;;GAIG;AACH,OAAO,EACL,aAAa,EACb,aAAa,EACb,WAAW,EACX,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AAIxB;;;;GAIG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAI1C;;;;;GAKG;AACH,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAIxD,6EAA6E;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAI9C;;;;;;GAMG;AACH,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}