cleartoship 0.4.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.
- package/LICENSE +21 -0
- package/README.md +264 -0
- package/action.yml +154 -0
- package/dist/banner.d.ts +1 -0
- package/dist/banner.js +14 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +103 -0
- package/dist/data/popular.d.ts +17 -0
- package/dist/data/popular.js +103 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/report.d.ts +15 -0
- package/dist/report.js +317 -0
- package/dist/scan.d.ts +24 -0
- package/dist/scan.js +81 -0
- package/dist/scanners/community.d.ts +2 -0
- package/dist/scanners/community.js +155 -0
- package/dist/scanners/dependencies.d.ts +21 -0
- package/dist/scanners/dependencies.js +549 -0
- package/dist/scanners/index.d.ts +8 -0
- package/dist/scanners/index.js +13 -0
- package/dist/scanners/rls.d.ts +7 -0
- package/dist/scanners/rls.js +531 -0
- package/dist/scanners/secrets.d.ts +2 -0
- package/dist/scanners/secrets.js +439 -0
- package/dist/scanners/server-actions.d.ts +2 -0
- package/dist/scanners/server-actions.js +466 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +10 -0
- package/dist/utils/ast.d.ts +13 -0
- package/dist/utils/ast.js +107 -0
- package/dist/utils/detect.d.ts +2 -0
- package/dist/utils/detect.js +77 -0
- package/dist/utils/entropy.d.ts +7 -0
- package/dist/utils/entropy.js +19 -0
- package/dist/utils/files.d.ts +13 -0
- package/dist/utils/files.js +154 -0
- package/dist/utils/osv.d.ts +26 -0
- package/dist/utils/osv.js +123 -0
- package/dist/utils/registry.d.ts +44 -0
- package/dist/utils/registry.js +195 -0
- package/dist/utils/sql.d.ts +20 -0
- package/dist/utils/sql.js +157 -0
- package/dist/utils/suppress.d.ts +19 -0
- package/dist/utils/suppress.js +56 -0
- package/dist/utils/traverse.d.ts +2 -0
- package/dist/utils/traverse.js +14 -0
- package/dist/vendor/gitleaks/rules.d.ts +13 -0
- package/dist/vendor/gitleaks/rules.js +1571 -0
- package/dist/vendor/guardvibe/index.d.ts +11 -0
- package/dist/vendor/guardvibe/index.js +60 -0
- package/dist/vendor/guardvibe/rules/advanced-security.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/advanced-security.js +418 -0
- package/dist/vendor/guardvibe/rules/ai-host-security.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/ai-host-security.js +190 -0
- package/dist/vendor/guardvibe/rules/ai-security.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/ai-security.js +413 -0
- package/dist/vendor/guardvibe/rules/ai-tool-runtime.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/ai-tool-runtime.js +194 -0
- package/dist/vendor/guardvibe/rules/api-security.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/api-security.js +154 -0
- package/dist/vendor/guardvibe/rules/auth.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/auth.js +222 -0
- package/dist/vendor/guardvibe/rules/cicd.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/cicd.js +108 -0
- package/dist/vendor/guardvibe/rules/core.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/core.js +484 -0
- package/dist/vendor/guardvibe/rules/cve-versions.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/cve-versions.js +1130 -0
- package/dist/vendor/guardvibe/rules/database.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/database.js +173 -0
- package/dist/vendor/guardvibe/rules/deployment.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/deployment.js +269 -0
- package/dist/vendor/guardvibe/rules/dockerfile.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/dockerfile.js +99 -0
- package/dist/vendor/guardvibe/rules/firebase.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/firebase.js +96 -0
- package/dist/vendor/guardvibe/rules/go.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/go.js +85 -0
- package/dist/vendor/guardvibe/rules/modern-stack.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/modern-stack.js +631 -0
- package/dist/vendor/guardvibe/rules/nextjs.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/nextjs.js +233 -0
- package/dist/vendor/guardvibe/rules/other-services.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/other-services.js +72 -0
- package/dist/vendor/guardvibe/rules/payments.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/payments.js +123 -0
- package/dist/vendor/guardvibe/rules/react-native.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/react-native.js +132 -0
- package/dist/vendor/guardvibe/rules/services.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/services.js +149 -0
- package/dist/vendor/guardvibe/rules/shell.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/shell.js +72 -0
- package/dist/vendor/guardvibe/rules/sql.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/sql.js +60 -0
- package/dist/vendor/guardvibe/rules/supply-chain.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/supply-chain.js +243 -0
- package/dist/vendor/guardvibe/rules/terraform.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/terraform.js +84 -0
- package/dist/vendor/guardvibe/rules/types.d.ts +14 -0
- package/dist/vendor/guardvibe/rules/types.js +11 -0
- package/dist/vendor/guardvibe/rules/web-security.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/web-security.js +258 -0
- package/examples/security.yml +23 -0
- package/package.json +70 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { read, rel, lineAt, snippetAt, languagesFor } from '../utils/files.js';
|
|
2
|
+
import { Suppressions } from '../utils/suppress.js';
|
|
3
|
+
import { GUARDVIBE_RULES, GUARDVIBE_ATTRIBUTION } from '../vendor/guardvibe/index.js';
|
|
4
|
+
import { emptyResult } from '../types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Upstream rules that restate a check ClearToShip already performs against the
|
|
7
|
+
* AST or the parsed schema. The AST version reasons about a whole function body
|
|
8
|
+
* instead of a fixed character window, so it is both more accurate and better
|
|
9
|
+
* located — running both would double-report and import the weaker result.
|
|
10
|
+
*
|
|
11
|
+
* These are disabled here rather than deleted from the vendored files, so those
|
|
12
|
+
* stay a faithful copy of upstream and re-vendoring is a straight overwrite.
|
|
13
|
+
*/
|
|
14
|
+
const SUPERSEDED = new Map([
|
|
15
|
+
['VG400', 'CTS033/CTS040'],
|
|
16
|
+
['VG401', 'CTS002'],
|
|
17
|
+
['VG402', 'CTS001'],
|
|
18
|
+
['VG411', 'CTS031'],
|
|
19
|
+
['VG420', 'CTS001'],
|
|
20
|
+
['VG427', 'CTS041'],
|
|
21
|
+
['VG439', 'CTS016'],
|
|
22
|
+
['VG604', 'CTS031'],
|
|
23
|
+
['VG627', 'CTS031'],
|
|
24
|
+
['VG631', 'CTS031'],
|
|
25
|
+
['VG655', 'CTS031'],
|
|
26
|
+
['VG656', 'CTS030'],
|
|
27
|
+
['VG657', 'CTS030'],
|
|
28
|
+
['VG665', 'CTS030'],
|
|
29
|
+
['VG671', 'CTS031'],
|
|
30
|
+
['VG708', 'CTS030'],
|
|
31
|
+
['VG754', 'CTS031'],
|
|
32
|
+
['VG953', 'CTS043'],
|
|
33
|
+
['VG998', 'CTS045'],
|
|
34
|
+
['VG437', 'CTS033'],
|
|
35
|
+
['VG860', 'CTS028'],
|
|
36
|
+
['VG874', 'CTS028'],
|
|
37
|
+
['VG876', 'CTS031'],
|
|
38
|
+
['VG952', 'CTS001'],
|
|
39
|
+
['VG960', 'CTS044'],
|
|
40
|
+
['VG1007', 'CTS003'],
|
|
41
|
+
['VG1010', 'CTS002'],
|
|
42
|
+
]);
|
|
43
|
+
/**
|
|
44
|
+
* Rules withheld because they misfire in this tool's context, measured against
|
|
45
|
+
* the fixtures plus the reference corpus. Each is recorded with its reason so
|
|
46
|
+
* the judgement can be revisited when upstream changes.
|
|
47
|
+
*/
|
|
48
|
+
const WITHHELD = new Map([
|
|
49
|
+
[
|
|
50
|
+
'VG543',
|
|
51
|
+
'matches `; DROP|DELETE|INSERT|…` anywhere in a .sql file, which is the normal shape of ' +
|
|
52
|
+
'every migration — 43 hits across the corpus, all false',
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
'VG540',
|
|
56
|
+
'flags any destructive DDL in a .sql file; migrations legitimately drop and alter objects',
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
'VG542',
|
|
60
|
+
'flags DELETE/UPDATE without WHERE in .sql files, where a full-table statement in a ' +
|
|
61
|
+
'migration is deliberate',
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
'VG863',
|
|
65
|
+
'a packaging lint (missing "files" field), not a security finding, and wrong for apps ' +
|
|
66
|
+
'rather than published libraries',
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
'VG1004',
|
|
70
|
+
'matches every `use server` module with an exported function; it asserts "no rate limiting" ' +
|
|
71
|
+
'without ever checking for it',
|
|
72
|
+
],
|
|
73
|
+
]);
|
|
74
|
+
/** Paths where a match is a fixture or documentation rather than shipped code. */
|
|
75
|
+
const NON_PRODUCTION_PATH = /(^|\/)(tests?|__tests__|__mocks__|__fixtures__|fixtures?|spec|specs|examples?|docs?|demo|samples?|e2e|cypress|playwright|stories)(\/|$)|\.(test|spec|stories|fixture)\.[a-z]+$/i;
|
|
76
|
+
/** Regexes over very large files are where catastrophic backtracking bites. */
|
|
77
|
+
const MAX_BYTES = 400_000;
|
|
78
|
+
/** Upstream severities already use our vocabulary; this just narrows the type. */
|
|
79
|
+
function severityOf(value) {
|
|
80
|
+
return ['critical', 'high', 'medium', 'low', 'info'].includes(value)
|
|
81
|
+
? value
|
|
82
|
+
: 'medium';
|
|
83
|
+
}
|
|
84
|
+
export const communityScanner = {
|
|
85
|
+
name: `Community ruleset (${GUARDVIBE_RULES.length - SUPERSEDED.size - WITHHELD.size} rules)`,
|
|
86
|
+
applies() {
|
|
87
|
+
return true;
|
|
88
|
+
},
|
|
89
|
+
async run(ctx) {
|
|
90
|
+
const result = emptyResult();
|
|
91
|
+
const active = GUARDVIBE_RULES.filter((r) => !SUPERSEDED.has(r.id) && !WITHHELD.has(r.id));
|
|
92
|
+
const seen = new Set();
|
|
93
|
+
let filesScanned = 0;
|
|
94
|
+
for (const file of ctx.files) {
|
|
95
|
+
const languages = languagesFor(file);
|
|
96
|
+
if (languages.length === 0)
|
|
97
|
+
continue;
|
|
98
|
+
const source = read(file);
|
|
99
|
+
if (source === null || source.length > MAX_BYTES)
|
|
100
|
+
continue;
|
|
101
|
+
const relPath = rel(ctx.root, file);
|
|
102
|
+
const fixture = NON_PRODUCTION_PATH.test(relPath);
|
|
103
|
+
const suppress = new Suppressions(source);
|
|
104
|
+
filesScanned++;
|
|
105
|
+
for (const rule of active) {
|
|
106
|
+
if (!rule.languages.some((l) => languages.includes(l)))
|
|
107
|
+
continue;
|
|
108
|
+
const re = rule.pattern;
|
|
109
|
+
re.lastIndex = 0;
|
|
110
|
+
let m;
|
|
111
|
+
let matches = 0;
|
|
112
|
+
while ((m = re.exec(source)) !== null) {
|
|
113
|
+
// A zero-width match would spin forever on a global regex.
|
|
114
|
+
if (m[0].length === 0) {
|
|
115
|
+
re.lastIndex++;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const line = lineAt(source, m.index);
|
|
119
|
+
const key = `${relPath}:${line}:${rule.id}`;
|
|
120
|
+
if (!seen.has(key) && !suppress.suppressed(line, rule.id)) {
|
|
121
|
+
seen.add(key);
|
|
122
|
+
result.findings.push({
|
|
123
|
+
id: rule.id,
|
|
124
|
+
severity: fixture ? 'low' : severityOf(rule.severity),
|
|
125
|
+
title: rule.name,
|
|
126
|
+
detail: rule.description +
|
|
127
|
+
(fixture ? ' (Path looks like tests or docs, so the severity is reduced.)' : ''),
|
|
128
|
+
fix: rule.fixCode ? `${rule.fix}\n\n${rule.fixCode}` : rule.fix,
|
|
129
|
+
file: relPath,
|
|
130
|
+
line,
|
|
131
|
+
snippet: snippetAt(source, line),
|
|
132
|
+
owasp: rule.owasp,
|
|
133
|
+
meta: {
|
|
134
|
+
source: 'guardvibe',
|
|
135
|
+
attribution: GUARDVIBE_ATTRIBUTION,
|
|
136
|
+
compliance: rule.compliance,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// One finding per rule per file is enough to act on.
|
|
141
|
+
if (++matches >= 3)
|
|
142
|
+
break;
|
|
143
|
+
if (!re.global)
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
result.checks.push({
|
|
149
|
+
label: `Community ruleset (${active.length} rules over ${filesScanned} files)`,
|
|
150
|
+
passed: result.findings.every((f) => f.severity !== 'critical'),
|
|
151
|
+
note: `${SUPERSEDED.size} superseded by ClearToShip's AST checks, ${WITHHELD.size} withheld as noisy`,
|
|
152
|
+
});
|
|
153
|
+
return result;
|
|
154
|
+
},
|
|
155
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Scanner } from '../types.js';
|
|
2
|
+
interface Declared {
|
|
3
|
+
name: string;
|
|
4
|
+
range: string;
|
|
5
|
+
ecosystem: 'npm' | 'pypi';
|
|
6
|
+
file: string;
|
|
7
|
+
line: number;
|
|
8
|
+
dev: boolean;
|
|
9
|
+
/** Harvested from an install command in prose rather than a manifest. */
|
|
10
|
+
fromProse?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Pulls package names out of install commands written in prose. Agent
|
|
14
|
+
* instruction files and READMEs are where a hallucinated name is copy-pasted
|
|
15
|
+
* from long before anyone adds it to a manifest, so they are worth reading.
|
|
16
|
+
*/
|
|
17
|
+
declare function collectFromProse(source: string, relPath: string): Declared[];
|
|
18
|
+
export declare const dependencyScanner: Scanner;
|
|
19
|
+
/** Exposed for tests: harvest package names from prose install commands. */
|
|
20
|
+
export declare const collectProseForTest: typeof collectFromProse;
|
|
21
|
+
export {};
|