hullproof 0.0.0-stage → 0.5.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 +222 -0
- package/NOTICE +32 -0
- package/README.md +313 -2
- package/checks/db.firebase-rules-not-open.json +24 -0
- package/checks/db.rls-every-table.json +25 -0
- package/checks/log.no-credential-in-logs.json +30 -0
- package/checks/secrets.no-env-file-committed.json +31 -0
- package/checks/secrets.no-secret-in-public-env.json +37 -0
- package/checks/storage.firebase-rules-not-open.json +22 -0
- package/checks/supply.no-untrusted-event-in-run.json +23 -0
- package/checks/web.cors-no-reflected-origin.json +31 -0
- package/detectors/ai.json +25 -0
- package/detectors/auth.json +27 -0
- package/detectors/ci.json +19 -0
- package/detectors/containers.json +8 -0
- package/detectors/data_stores.json +38 -0
- package/detectors/frameworks.json +94 -0
- package/detectors/hosting.json +44 -0
- package/detectors/iac.json +43 -0
- package/detectors/languages.json +48 -0
- package/detectors/mobile.json +22 -0
- package/detectors/package_managers.json +39 -0
- package/detectors/payments.json +19 -0
- package/detectors/runtimes.json +17 -0
- package/dist/checks/code/admin-role.js +46 -0
- package/dist/checks/code/owner-filter.js +91 -0
- package/dist/checks/code/shared.js +84 -0
- package/dist/checks/code/webhook-verify.js +77 -0
- package/dist/checks/context.js +64 -0
- package/dist/checks/map/config-checks.js +74 -0
- package/dist/checks/map/entry-checks.js +97 -0
- package/dist/checks/map/shared.js +44 -0
- package/dist/checks/ops.js +307 -0
- package/dist/checks/pro-map.js +1 -0
- package/dist/checks/run.js +92 -0
- package/dist/checks/types.js +1 -0
- package/dist/ci/comment.js +44 -0
- package/dist/ci/sarif-check.js +157 -0
- package/dist/ci/sarif.js +61 -0
- package/dist/cli.js +358 -0
- package/dist/commands/ci.js +115 -0
- package/dist/commands/git.js +58 -0
- package/dist/commands/hooks.js +30 -0
- package/dist/commands/hunt-pro.js +1 -0
- package/dist/commands/hunt.js +86 -0
- package/dist/commands/map.js +33 -0
- package/dist/commands/mcp.js +17 -0
- package/dist/commands/pack.js +48 -0
- package/dist/commands/prove-pro.js +1 -0
- package/dist/commands/prove.js +173 -0
- package/dist/commands/report-gate.js +92 -0
- package/dist/commands/scan.js +158 -0
- package/dist/confirm/agreement.js +18 -0
- package/dist/confirm/chain.js +84 -0
- package/dist/confirm/confirm.js +112 -0
- package/dist/confirm/honesty.js +75 -0
- package/dist/confirm/lead.js +92 -0
- package/dist/confirm/merge.js +88 -0
- package/dist/confirm/query.js +145 -0
- package/dist/confirm/rule.js +86 -0
- package/dist/confirm/store.js +42 -0
- package/dist/confirm/summary.js +6 -0
- package/dist/confirm/synthesis.js +28 -0
- package/dist/discover/detectors.js +50 -0
- package/dist/discover/discover.js +270 -0
- package/dist/discover/extract.js +282 -0
- package/dist/discover/git.js +40 -0
- package/dist/discover/kit-files.js +47 -0
- package/dist/discover/walk.js +110 -0
- package/dist/env.js +57 -0
- package/dist/gate/gate.js +173 -0
- package/dist/hooks/install.js +153 -0
- package/dist/hooks/run.js +112 -0
- package/dist/map/analyze.js +201 -0
- package/dist/map/build.js +253 -0
- package/dist/map/candidates.js +49 -0
- package/dist/map/cblocks.js +77 -0
- package/dist/map/config.js +291 -0
- package/dist/map/dotnet.js +242 -0
- package/dist/map/entry.js +78 -0
- package/dist/map/expo.js +190 -0
- package/dist/map/go.js +292 -0
- package/dist/map/js-express.js +143 -0
- package/dist/map/js-misc.js +295 -0
- package/dist/map/js-next.js +292 -0
- package/dist/map/laravel.js +389 -0
- package/dist/map/lex-more.js +305 -0
- package/dist/map/lex.js +425 -0
- package/dist/map/light-index.js +191 -0
- package/dist/map/light-profiles.js +324 -0
- package/dist/map/light.js +574 -0
- package/dist/map/local-vocab.js +27 -0
- package/dist/map/mobile.js +326 -0
- package/dist/map/py-django.js +281 -0
- package/dist/map/py.js +391 -0
- package/dist/map/rails.js +391 -0
- package/dist/map/repo.js +178 -0
- package/dist/map/rust.js +312 -0
- package/dist/map/spring.js +207 -0
- package/dist/map/taint.js +96 -0
- package/dist/map/touches.js +355 -0
- package/dist/map/vocab.js +88 -0
- package/dist/mapping/controls.js +34 -0
- package/dist/mapping/dependency-policy.js +44 -0
- package/dist/mapping/map.js +44 -0
- package/dist/mapping/maps/gitleaks.json +12 -0
- package/dist/mapping/maps/osv-scanner.json +7 -0
- package/dist/mapping/maps/trivy.json +13 -0
- package/dist/mcp/confine.js +59 -0
- package/dist/mcp/protocol.js +140 -0
- package/dist/mcp/schema.js +43 -0
- package/dist/mcp/server.js +37 -0
- package/dist/mcp/tools.js +238 -0
- package/dist/packs/coverage.js +79 -0
- package/dist/packs/manifest.js +36 -0
- package/dist/packs/validate.js +271 -0
- package/dist/prove/container.js +276 -0
- package/dist/prove/exec-main.js +20 -0
- package/dist/prove/executor.js +149 -0
- package/dist/prove/local.js +131 -0
- package/dist/prove/probes.js +65 -0
- package/dist/prove/recipe.js +278 -0
- package/dist/prove/run.js +136 -0
- package/dist/prove/runner.js +1 -0
- package/dist/prove/safety.js +126 -0
- package/dist/prove/spec.js +357 -0
- package/dist/prove/store.js +65 -0
- package/dist/report/build.js +297 -0
- package/dist/report/entry-points.js +75 -0
- package/dist/report/hunt-section.js +71 -0
- package/dist/report/kit.js +41 -0
- package/dist/report/load.js +35 -0
- package/dist/report/messages.js +273 -0
- package/dist/report/prove-section.js +65 -0
- package/dist/report/report.js +156 -0
- package/dist/report/surface.js +21 -0
- package/dist/report/test-code.js +51 -0
- package/dist/report/tiers.js +44 -0
- package/dist/scanners/common.js +162 -0
- package/dist/scanners/gitleaks.js +90 -0
- package/dist/scanners/index.js +113 -0
- package/dist/scanners/osv-scanner.js +77 -0
- package/dist/scanners/run-targets.js +34 -0
- package/dist/scanners/secret-noise.js +89 -0
- package/dist/scanners/semgrep.js +142 -0
- package/dist/scanners/trivy.js +65 -0
- package/dist/scanners/types.js +1 -0
- package/dist/types/check.js +1 -0
- package/dist/types/codemap.js +1 -0
- package/dist/types/findings.js +5 -0
- package/dist/types/hunt.js +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/prove.js +1 -0
- package/dist/types/results.js +2 -0
- package/dist/types/scan.js +1 -0
- package/dist/types/stack.js +5 -0
- package/dist/util.js +71 -0
- package/dist/version.js +2 -0
- package/package.json +46 -4
- package/packs/android/fixture-result.json +43 -0
- package/packs/android/pack.json +56 -0
- package/packs/dotnet/fixture-result.json +62 -0
- package/packs/dotnet/pack.json +88 -0
- package/packs/expo/answer-key.json +51 -0
- package/packs/expo/fixture-result.json +62 -0
- package/packs/expo/pack.json +79 -0
- package/packs/firebase/answer-key.json +27 -0
- package/packs/firebase/fixture-result.json +43 -0
- package/packs/firebase/pack.json +58 -0
- package/packs/go/answer-key.json +101 -0
- package/packs/go/fixture-result.json +64 -0
- package/packs/go/pack.json +99 -0
- package/packs/ios/fixture-result.json +43 -0
- package/packs/ios/pack.json +55 -0
- package/packs/laravel/answer-key.json +84 -0
- package/packs/laravel/fixture-result.json +60 -0
- package/packs/laravel/pack.json +91 -0
- package/packs/node-ts/answer-key.json +220 -0
- package/packs/node-ts/fixture-result.json +114 -0
- package/packs/node-ts/pack.json +134 -0
- package/packs/python/answer-key.json +195 -0
- package/packs/python/fixture-result.json +119 -0
- package/packs/python/pack.json +121 -0
- package/packs/rails/answer-key.json +84 -0
- package/packs/rails/fixture-result.json +60 -0
- package/packs/rails/pack.json +78 -0
- package/packs/rust/fixture-result.json +43 -0
- package/packs/rust/pack.json +69 -0
- package/packs/spring/fixture-result.json +63 -0
- package/packs/spring/pack.json +103 -0
- package/packs/supabase/answer-key.json +52 -0
- package/packs/supabase/fixture-result.json +58 -0
- package/packs/supabase/pack.json +60 -0
- package/rules/gitleaks.toml +256 -0
- package/rules/semgrep/api-route-no-auth.yaml +31 -0
- package/rules/semgrep/cors-reflect-origin.yaml +48 -0
- package/rules/semgrep/eval-or-new-function.yaml +185 -0
- package/rules/semgrep/log-session-token.yaml +48 -0
- package/rules/semgrep/math-random-tokens.yaml +24 -0
- package/rules/semgrep/next-public-secret-name.yaml +32 -0
- package/rules/semgrep/postgrest-filter-injection.yaml +78 -0
- package/rules/semgrep/raw-sql-concat.yaml +42 -0
- package/rules/semgrep/read-by-id-no-owner.yaml +49 -0
- package/rules/semgrep/server-action-no-auth.yaml +26 -0
- package/rules/semgrep/sql-variants.yaml +99 -0
- package/rules/semgrep/ssrf-unguarded-url.yaml +137 -0
- package/rules/semgrep/subprocess-injection.yaml +163 -0
- package/rules/semgrep/taint-go.yaml +333 -0
- package/rules/semgrep/taint-java.yaml +317 -0
- package/rules/semgrep/taint-javascript.yaml +412 -0
- package/rules/semgrep/taint-php.yaml +361 -0
- package/rules/semgrep/taint-python.yaml +414 -0
- package/rules/semgrep/tool-endpoint-admin-client.yaml +86 -0
- package/rules/semgrep/user-metadata-authz.yaml +22 -0
- package/rules/semgrep/verify-result-unused.yaml +62 -0
- package/rules/semgrep/webhook-parse-before-verify.yaml +29 -0
- package/rules/test-artifact-ids.json +4 -0
- package/rules/tiers.json +427 -0
- package/schemas/check-results.schema.json +55 -0
- package/schemas/check.schema.json +41 -0
- package/schemas/codemap.schema.json +216 -0
- package/schemas/findings.schema.json +1048 -0
- package/schemas/hunt-output.schema.json +46 -0
- package/schemas/hunt-result.schema.json +58 -0
- package/schemas/results.schema.json +17 -0
- package/schemas/scan-hits.schema.json +32 -0
- package/schemas/scan-meta.schema.json +25 -0
- package/schemas/stack.schema.json +93 -0
- package/src/checks/README.md +32 -0
- package/src/checks/code/admin-role.ts +49 -0
- package/src/checks/code/owner-filter.ts +95 -0
- package/src/checks/code/shared.ts +66 -0
- package/src/checks/code/webhook-verify.ts +75 -0
- package/src/checks/context.ts +72 -0
- package/src/checks/map/config-checks.ts +79 -0
- package/src/checks/map/entry-checks.ts +116 -0
- package/src/checks/map/shared.ts +63 -0
- package/src/checks/ops.ts +280 -0
- package/src/checks/pro-map.ts +4 -0
- package/src/checks/run.ts +80 -0
- package/src/checks/types.ts +12 -0
- package/src/ci/comment.ts +52 -0
- package/src/ci/sarif-check.ts +100 -0
- package/src/ci/sarif.ts +72 -0
- package/src/cli.ts +273 -0
- package/src/commands/ci.ts +104 -0
- package/src/commands/git.ts +59 -0
- package/src/commands/hooks.ts +27 -0
- package/src/commands/hunt-pro.ts +4 -0
- package/src/commands/hunt.ts +68 -0
- package/src/commands/map.ts +27 -0
- package/src/commands/mcp.ts +18 -0
- package/src/commands/pack.ts +34 -0
- package/src/commands/prove-pro.ts +4 -0
- package/src/commands/prove.ts +126 -0
- package/src/commands/report-gate.ts +75 -0
- package/src/commands/scan.ts +156 -0
- package/src/confirm/agreement.ts +23 -0
- package/src/confirm/chain.ts +67 -0
- package/src/confirm/confirm.ts +107 -0
- package/src/confirm/honesty.ts +65 -0
- package/src/confirm/lead.ts +76 -0
- package/src/confirm/merge.ts +89 -0
- package/src/confirm/query.ts +126 -0
- package/src/confirm/rule.ts +76 -0
- package/src/confirm/store.ts +42 -0
- package/src/confirm/summary.ts +9 -0
- package/src/confirm/synthesis.ts +33 -0
- package/src/discover/detectors.ts +71 -0
- package/src/discover/discover.ts +253 -0
- package/src/discover/extract.ts +235 -0
- package/src/discover/git.ts +31 -0
- package/src/discover/kit-files.ts +37 -0
- package/src/discover/walk.ts +88 -0
- package/src/env.ts +51 -0
- package/src/gate/gate.ts +169 -0
- package/src/hooks/install.ts +142 -0
- package/src/hooks/run.ts +121 -0
- package/src/map/analyze.ts +219 -0
- package/src/map/build.ts +206 -0
- package/src/map/candidates.ts +47 -0
- package/src/map/cblocks.ts +55 -0
- package/src/map/config.ts +207 -0
- package/src/map/dotnet.ts +205 -0
- package/src/map/entry.ts +80 -0
- package/src/map/expo.ts +171 -0
- package/src/map/go.ts +271 -0
- package/src/map/js-express.ts +135 -0
- package/src/map/js-misc.ts +221 -0
- package/src/map/js-next.ts +282 -0
- package/src/map/laravel.ts +327 -0
- package/src/map/lex-more.ts +152 -0
- package/src/map/lex.ts +274 -0
- package/src/map/light-index.ts +134 -0
- package/src/map/light-profiles.ts +363 -0
- package/src/map/light.ts +484 -0
- package/src/map/local-vocab.ts +30 -0
- package/src/map/mobile.ts +263 -0
- package/src/map/py-django.ts +239 -0
- package/src/map/py.ts +329 -0
- package/src/map/rails.ts +291 -0
- package/src/map/repo.ts +150 -0
- package/src/map/rust.ts +256 -0
- package/src/map/spring.ts +185 -0
- package/src/map/taint.ts +86 -0
- package/src/map/touches.ts +296 -0
- package/src/map/vocab.ts +106 -0
- package/src/mapping/controls.ts +47 -0
- package/src/mapping/dependency-policy.ts +55 -0
- package/src/mapping/map.ts +59 -0
- package/src/mapping/maps/gitleaks.json +12 -0
- package/src/mapping/maps/osv-scanner.json +7 -0
- package/src/mapping/maps/trivy.json +13 -0
- package/src/mcp/confine.ts +44 -0
- package/src/mcp/protocol.ts +144 -0
- package/src/mcp/schema.ts +35 -0
- package/src/mcp/server.ts +32 -0
- package/src/mcp/tools.ts +235 -0
- package/src/packs/coverage.ts +98 -0
- package/src/packs/manifest.ts +97 -0
- package/src/packs/validate.ts +204 -0
- package/src/prove/container.ts +237 -0
- package/src/prove/exec-main.ts +16 -0
- package/src/prove/executor.ts +130 -0
- package/src/prove/local.ts +100 -0
- package/src/prove/probes.ts +68 -0
- package/src/prove/recipe.ts +215 -0
- package/src/prove/run.ts +124 -0
- package/src/prove/runner.ts +28 -0
- package/src/prove/safety.ts +98 -0
- package/src/prove/spec.ts +256 -0
- package/src/prove/store.ts +48 -0
- package/src/report/build.ts +304 -0
- package/src/report/entry-points.ts +78 -0
- package/src/report/hunt-section.ts +67 -0
- package/src/report/kit.ts +59 -0
- package/src/report/load.ts +49 -0
- package/src/report/messages.ts +270 -0
- package/src/report/prove-section.ts +61 -0
- package/src/report/report.ts +160 -0
- package/src/report/surface.ts +32 -0
- package/src/report/test-code.ts +56 -0
- package/src/report/tiers.ts +50 -0
- package/src/scanners/common.ts +149 -0
- package/src/scanners/gitleaks.ts +89 -0
- package/src/scanners/index.ts +103 -0
- package/src/scanners/osv-scanner.ts +90 -0
- package/src/scanners/run-targets.ts +35 -0
- package/src/scanners/secret-noise.ts +80 -0
- package/src/scanners/semgrep.ts +139 -0
- package/src/scanners/trivy.ts +66 -0
- package/src/scanners/types.ts +98 -0
- package/src/types/check.ts +52 -0
- package/src/types/codemap.ts +142 -0
- package/src/types/findings.ts +107 -0
- package/src/types/hunt.ts +97 -0
- package/src/types/index.ts +10 -0
- package/src/types/prove.ts +83 -0
- package/src/types/results.ts +15 -0
- package/src/types/scan.ts +63 -0
- package/src/types/stack.ts +61 -0
- package/src/util.ts +59 -0
- package/src/version.ts +2 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Semgrep adapter. Runs the Hullproof rules in rules/semgrep/, plus an optional external rule pack folder (for example a rule pack of your own),
|
|
2
|
+
// on the files git tracks, named one by one, with --no-git-ignore so Semgrep itself never calls git and the repository's
|
|
3
|
+
// own ignore files cannot hide a tracked file.
|
|
4
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { basename, join } from 'node:path';
|
|
6
|
+
import { cmp, sortUnique } from '../util.js';
|
|
7
|
+
import { dirSha256, fileSha256, isRegularFile, relPath, sha256, sourceLine } from './common.js';
|
|
8
|
+
import { inEdition, loadControls } from '../mapping/controls.js';
|
|
9
|
+
import { mapHit } from '../mapping/map.js';
|
|
10
|
+
const yamlIn = (dir) => readdirSync(dir).filter((n) => /\.ya?ml$/.test(n)).sort(cmp);
|
|
11
|
+
// The rule files Semgrep runs, by file name: the engine's folder, then the pack. A pack file with the same name as an
|
|
12
|
+
// engine file replaces it, so a pack may be a whole kit rules folder (identical files change nothing) or only the
|
|
13
|
+
// files it adds. Throws when the pack folder is missing or holds no rule file.
|
|
14
|
+
export function ruleFiles(dir, pack = null) {
|
|
15
|
+
const byName = new Map(yamlIn(dir).map((n) => [n, join(dir, n)]));
|
|
16
|
+
if (pack) {
|
|
17
|
+
if (!existsSync(pack))
|
|
18
|
+
throw new Error(`the rule pack folder ${basename(pack)} does not exist`);
|
|
19
|
+
const names = yamlIn(pack);
|
|
20
|
+
if (!names.length)
|
|
21
|
+
throw new Error(`the rule pack folder ${basename(pack)} holds no .yaml rule file`);
|
|
22
|
+
for (const n of names)
|
|
23
|
+
byName.set(n, join(pack, n));
|
|
24
|
+
}
|
|
25
|
+
return [...byName.keys()].sort(cmp).map((n) => byName.get(n));
|
|
26
|
+
}
|
|
27
|
+
// Reads rule ids and metadata.sec_ids from the rule files. The kit writes `- id: x` and `sec_ids: [A, B]` on one line
|
|
28
|
+
// each; test/scanners.test.ts checks every rule has both.
|
|
29
|
+
export function readRules(dir, pack = null) {
|
|
30
|
+
const out = [];
|
|
31
|
+
for (const path of ruleFiles(dir, pack)) {
|
|
32
|
+
const f = basename(path);
|
|
33
|
+
const version = 'sha256:' + fileSha256(path).slice(0, 16);
|
|
34
|
+
let cur = null;
|
|
35
|
+
for (const line of readFileSync(path, 'utf8').split('\n')) {
|
|
36
|
+
const id = /^\s*-\s+id:\s*([\w.-]+)\s*$/.exec(line)?.[1];
|
|
37
|
+
if (id) {
|
|
38
|
+
cur = { id, file: f, sec_ids: [], version };
|
|
39
|
+
out.push(cur);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const ids = /^\s*sec_ids:\s*\[(.*)\]\s*$/.exec(line)?.[1];
|
|
43
|
+
if (ids !== undefined && cur)
|
|
44
|
+
cur.sec_ids = sortUnique(ids.split(',').map((s) => s.trim()).filter(Boolean));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
const TIMEOUT_SECONDS = '30';
|
|
50
|
+
// Minified or generated files (a line longer than this) make Semgrep time out at random, which breaks byte identical
|
|
51
|
+
// output. They are left out by this fixed rule and listed as tool errors of type "skipped: minified".
|
|
52
|
+
const MAX_LINE = 1000;
|
|
53
|
+
const minified = (p) => {
|
|
54
|
+
try {
|
|
55
|
+
return readFileSync(p, 'utf8').split('\n').some((l) => l.length > MAX_LINE);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const MAX_TARGET_BYTES = '1000000';
|
|
62
|
+
export const adapter = {
|
|
63
|
+
id: 'semgrep',
|
|
64
|
+
tool: 'semgrep',
|
|
65
|
+
versionCommand: ['semgrep', '--version'],
|
|
66
|
+
testedVersions: '1.176.x',
|
|
67
|
+
needsNetwork: false,
|
|
68
|
+
applies: (stack) => stack.languages.length > 0 || stack.ci.length > 0,
|
|
69
|
+
notApplicableReason: (o) => (adapter.applies(o.stack) ? null : 'No source language or CI workflow was found, so there is nothing for the Semgrep rules to read.'),
|
|
70
|
+
configFiles: (ctx) => [join(ctx.rulesDir ?? '', 'semgrep')],
|
|
71
|
+
command: (ctx) => ['semgrep', 'scan', '--config', join(ctx.rulesDir ?? '', 'semgrep'), '--json', '--quiet',
|
|
72
|
+
'--metrics=off', '--disable-version-check', '--no-git-ignore', '--timeout', TIMEOUT_SECONDS, '--timeout-threshold', '3',
|
|
73
|
+
'--max-target-bytes', MAX_TARGET_BYTES],
|
|
74
|
+
parse: (stdout, ctx) => parseSemgrep(stdout, ctx, readRules(join(ctx.rulesDir ?? '', 'semgrep'), ctx.rulePack ?? null)).hits,
|
|
75
|
+
possibleSecIds: (o) => sortUnique(inEdition(readRules(join(o.rulesDir, 'semgrep'), o.rulePack ?? null).flatMap((r) => r.sec_ids), o.edition, loadControls(o.controlsPath))),
|
|
76
|
+
execute(o, env) {
|
|
77
|
+
const engineRules = join(env.rulesDir, 'semgrep');
|
|
78
|
+
const pack = o.rulePack ?? null;
|
|
79
|
+
// With a pack, the merged rule files go into one folder in the scratch space, so Semgrep reads one --config folder
|
|
80
|
+
// and the config sha256 covers exactly the rules that ran.
|
|
81
|
+
let rulesDir = engineRules;
|
|
82
|
+
if (pack) {
|
|
83
|
+
const rules = readRules(engineRules, pack);
|
|
84
|
+
const twice = rules.map((r) => r.id).filter((id, i, a) => a.indexOf(id) !== i);
|
|
85
|
+
if (twice.length)
|
|
86
|
+
throw new Error(`rule id ${twice[0]} is in two rule files once the pack is added`);
|
|
87
|
+
rulesDir = join(env.scratch, 'rules');
|
|
88
|
+
mkdirSync(rulesDir);
|
|
89
|
+
for (const f of ruleFiles(engineRules, pack))
|
|
90
|
+
copyFileSync(f, join(rulesDir, basename(f)));
|
|
91
|
+
}
|
|
92
|
+
// ponytail: every tracked file goes on the command line; split into batches if a repository ever passes ARG_MAX.
|
|
93
|
+
const listed = o.targets ? new Set(o.targets) : env.tracked;
|
|
94
|
+
const files = listed ? [...listed].filter((f) => isRegularFile(join(o.repo, f))).sort(cmp) : [];
|
|
95
|
+
const skipped = files.filter((f) => /\.(js|mjs|cjs|ts|jsx|tsx|json|ya?ml|html|py)$/.test(f) && minified(join(o.repo, f)));
|
|
96
|
+
const targets = listed ? files.filter((f) => !skipped.includes(f)) : ['.'];
|
|
97
|
+
const base = adapter.command({ ...o, rulesDir: env.rulesDir });
|
|
98
|
+
const shown = [...base.map((a) => (a === join(env.rulesDir, 'semgrep') ? (pack ? '<engine>/rules/semgrep + <rule-pack>' : '<engine>/rules/semgrep') : a)),
|
|
99
|
+
listed ? `<${targets.length} ${o.targets ? 'listed' : 'tracked'} files, list sha256 ${sha256(targets.join('\n')).slice(0, 16)}>` : '.'];
|
|
100
|
+
// No file to read: Semgrep with no target would read the whole folder, so it is not called.
|
|
101
|
+
if (listed && targets.length === 0)
|
|
102
|
+
return { hits: [], toolErrors: [], commands: [shown], configSha256: dirSha256(rulesDir), scanned: [], exitCode: 0 };
|
|
103
|
+
const r = env.run([...base.map((a) => (a === join(env.rulesDir, 'semgrep') ? rulesDir : a)), ...targets]);
|
|
104
|
+
let parsed;
|
|
105
|
+
try {
|
|
106
|
+
parsed = JSON.parse(r.stdout);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
throw new Error(`semgrep printed no JSON (exit ${r.status})`);
|
|
110
|
+
}
|
|
111
|
+
const out = parseSemgrep(r.stdout, o, readRules(rulesDir));
|
|
112
|
+
return {
|
|
113
|
+
hits: out.hits, toolErrors: [...out.errors, ...skipped.map((file) => ({ type: 'skipped: minified', file, rule_id: '' }))], commands: [shown], configSha256: dirSha256(rulesDir),
|
|
114
|
+
scanned: sortUnique(parsed.paths.scanned.map((p) => relPath(p, o.repo))), exitCode: r.status,
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
export function parseSemgrep(stdout, o, rules) {
|
|
119
|
+
const j = JSON.parse(stdout);
|
|
120
|
+
const controls = loadControls(o.controlsPath);
|
|
121
|
+
// check_id is prefixed with the rules folder written as a dotted path; the rule id is the known id it ends with.
|
|
122
|
+
const ruleOf = (checkId) => rules.find((r) => checkId === r.id || checkId.endsWith('.' + r.id));
|
|
123
|
+
const hits = j.results.map((x) => {
|
|
124
|
+
const rule = ruleOf(x.check_id);
|
|
125
|
+
const rule_id = rule?.id ?? x.check_id.split('.').pop();
|
|
126
|
+
const file = relPath(x.path, o.repo);
|
|
127
|
+
const meta = x.extra.metadata?.sec_ids ?? rule?.sec_ids ?? [];
|
|
128
|
+
const m = mapHit({ tool: 'semgrep', rule_id, file, metadataSecIds: meta }, o.edition, controls);
|
|
129
|
+
const secretRule = meta.some((id) => id.startsWith('SEC-SECRETS-'));
|
|
130
|
+
return {
|
|
131
|
+
tool: 'semgrep', rule_id, file, line: x.start.line, end_line: x.end.line, sec_ids: m.sec_ids,
|
|
132
|
+
severity_hint: x.extra.severity ?? null, message_key: `semgrep:${rule_id}`, rule_version: rule?.version ?? 'unknown',
|
|
133
|
+
evidence: secretRule ? null : sourceLine(o.repo, file, x.start.line), mapped_by: m.mapped_by, unmapped: m.sec_ids.length === 0,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
const errors = j.errors.map((e) => ({
|
|
137
|
+
type: typeof e.type === 'string' ? e.type : Array.isArray(e.type) ? String(e.type[0]) : 'unknown',
|
|
138
|
+
file: e.path ? relPath(e.path, o.repo) : '',
|
|
139
|
+
rule_id: e.rule_id ? (ruleOf(e.rule_id)?.id ?? e.rule_id.split('.').pop()) : '',
|
|
140
|
+
}));
|
|
141
|
+
return { hits, errors };
|
|
142
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Trivy adapter: `trivy config` only, so misconfiguration checks on IaC, Kubernetes, Helm and Dockerfiles. No image is
|
|
2
|
+
// pulled. The checks are the ones built into the Trivy binary (--skip-check-update, an empty cache folder), so the rule
|
|
3
|
+
// set is fixed by the Trivy version, and network is blocked unless allowed (Terraform remote modules then do not load).
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { cmp, sortUnique } from '../util.js';
|
|
6
|
+
import { isRegularFile, relPath, sourceLine } from './common.js';
|
|
7
|
+
import { inEdition, loadControls } from '../mapping/controls.js';
|
|
8
|
+
import { mapHit, tableSecIds } from '../mapping/map.js';
|
|
9
|
+
// Pinned so a Trivy update cannot add a scanner silently. The list is Trivy 0.75.0's default.
|
|
10
|
+
const MISCONFIG_SCANNERS = 'azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot,ansible';
|
|
11
|
+
export const adapter = {
|
|
12
|
+
id: 'trivy',
|
|
13
|
+
tool: 'trivy',
|
|
14
|
+
versionCommand: ['trivy', '--version'],
|
|
15
|
+
testedVersions: '0.75.x',
|
|
16
|
+
needsNetwork: false,
|
|
17
|
+
applies: (stack) => stack.iac.length > 0 || stack.containers.length > 0,
|
|
18
|
+
notApplicableReason: (o) => (adapter.applies(o.stack) ? null : 'No infrastructure as code, Kubernetes, Helm or container file was found, so Trivy has nothing to check.'),
|
|
19
|
+
configFiles: () => [],
|
|
20
|
+
command: () => ['trivy', 'config', '--format', 'json', '--quiet', '--skip-check-update', '--skip-version-check',
|
|
21
|
+
'--disable-telemetry', '--include-non-failures', '--misconfig-scanners', MISCONFIG_SCANNERS, '--cache-dir', '<empty-dir>', '.'],
|
|
22
|
+
parse: (stdout, ctx) => parseTrivy(stdout, ctx, 'unknown').hits,
|
|
23
|
+
possibleSecIds: (o) => sortUnique(inEdition(tableSecIds('trivy'), o.edition, loadControls(o.controlsPath))),
|
|
24
|
+
execute(o, env) {
|
|
25
|
+
const argv = adapter.command(o).map((a) => (a === '<empty-dir>' ? env.scratch : a));
|
|
26
|
+
const shown = adapter.command(o);
|
|
27
|
+
const r = env.run(argv);
|
|
28
|
+
if (r.status !== 0)
|
|
29
|
+
throw new Error(`trivy exited with ${r.status ?? r.error}`);
|
|
30
|
+
const out = parseTrivy(r.stdout, o, `trivy ${env.version} built in checks`, env.tracked);
|
|
31
|
+
return { hits: out.hits, commands: [shown], configSha256: null, scanned: out.scanned, exitCode: r.status, toolErrors: [] };
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
// Terraform results come per module folder as well as per file; a folder target stands for the tracked .tf files in it.
|
|
35
|
+
export function parseTrivy(stdout, o, ruleVersion, tracked = null) {
|
|
36
|
+
const controls = loadControls(o.controlsPath);
|
|
37
|
+
const j = JSON.parse(stdout);
|
|
38
|
+
const hits = [];
|
|
39
|
+
const scanned = new Set();
|
|
40
|
+
for (const res of j.Results ?? []) {
|
|
41
|
+
if (res.Class !== 'config')
|
|
42
|
+
continue;
|
|
43
|
+
const target = relPath(res.Target, o.repo);
|
|
44
|
+
if (tracked && !isRegularFile(join(o.repo, target))) {
|
|
45
|
+
const prefix = target === '.' ? '' : target + '/';
|
|
46
|
+
for (const f of tracked)
|
|
47
|
+
if (f.startsWith(prefix) && !f.slice(prefix.length).includes('/') && /\.tf(\.json)?$/.test(f))
|
|
48
|
+
scanned.add(f);
|
|
49
|
+
}
|
|
50
|
+
else
|
|
51
|
+
scanned.add(target);
|
|
52
|
+
for (const m of res.Misconfigurations ?? []) {
|
|
53
|
+
if (m.Status !== 'FAIL')
|
|
54
|
+
continue;
|
|
55
|
+
const line = m.CauseMetadata?.StartLine ?? 0;
|
|
56
|
+
const mapped = mapHit({ tool: 'trivy', rule_id: m.ID, file: target }, o.edition, controls);
|
|
57
|
+
hits.push({
|
|
58
|
+
tool: 'trivy', rule_id: m.ID, file: target, line, end_line: m.CauseMetadata?.EndLine ?? line, sec_ids: mapped.sec_ids,
|
|
59
|
+
severity_hint: m.Severity ?? null, message_key: `trivy:${m.ID}`, rule_version: ruleVersion,
|
|
60
|
+
evidence: sourceLine(o.repo, target, line), mapped_by: mapped.mapped_by, unmapped: !mapped.sec_ids.length,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { hits, scanned: [...scanned].sort(cmp) };
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The findings record (the JSON inside the hullproof-findings block of report.findings.md), schema 1.1.0.
|
|
2
|
+
// The schema file is schemas/findings.schema.json, a copy of the kit's docs/hullproof/findings.schema.json; the kit file is the
|
|
3
|
+
// source of truth and the copy is checked by test/schemas.test.ts. Field limits (lengths, counts, patterns) live in the schema;
|
|
4
|
+
// cross field rules live in the kit's validate-findings.mjs. The engine writes schema_version "1.1.0".
|
|
5
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { STACK_CATEGORIES } from './stack.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// stack.json, written by `hullproof discover`. JSON schema: schemas/stack.schema.json. Documented in docs/INTERFACES.md.
|
|
2
|
+
export const STACK_CATEGORIES = [
|
|
3
|
+
'languages', 'runtimes', 'frameworks', 'package_managers', 'data_stores', 'auth', 'payments', 'ai',
|
|
4
|
+
'hosting', 'iac', 'containers', 'ci', 'mobile',
|
|
5
|
+
];
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Small helpers shared by every module.
|
|
2
|
+
// Code unit order. Never localeCompare: its result depends on the machine's locale.
|
|
3
|
+
export const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
4
|
+
export const sortUnique = (xs) => [...new Set(xs)].sort(cmp);
|
|
5
|
+
function sortKeys(v) {
|
|
6
|
+
if (Array.isArray(v))
|
|
7
|
+
return v.map(sortKeys);
|
|
8
|
+
if (v && typeof v === 'object') {
|
|
9
|
+
const out = {};
|
|
10
|
+
for (const k of Object.keys(v).sort(cmp)) {
|
|
11
|
+
const x = v[k];
|
|
12
|
+
if (x !== undefined)
|
|
13
|
+
out[k] = sortKeys(x);
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
return v;
|
|
18
|
+
}
|
|
19
|
+
// The engine's one JSON writer: sorted keys, two space indent, LF, final newline. Same value, same bytes.
|
|
20
|
+
export const stableJson = (v) => JSON.stringify(sortKeys(v), null, 2) + '\n';
|
|
21
|
+
// The same sorted JSON on one line, for a block that must stay under a size limit.
|
|
22
|
+
export const stableJsonCompact = (v) => JSON.stringify(sortKeys(v)) + '\n';
|
|
23
|
+
// Glob to RegExp. Supports *, **, ?, [...] and {a,b}. A pattern with no slash matches the file name at any depth;
|
|
24
|
+
// a pattern with a slash matches the whole repository relative path.
|
|
25
|
+
function globBody(p) {
|
|
26
|
+
let out = '';
|
|
27
|
+
for (let i = 0; i < p.length; i++) {
|
|
28
|
+
const c = p[i];
|
|
29
|
+
if (c === '*') {
|
|
30
|
+
if (p[i + 1] === '*') {
|
|
31
|
+
if (p[i + 2] === '/') {
|
|
32
|
+
out += '(?:.*/)?';
|
|
33
|
+
i += 2;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
out += '.*';
|
|
37
|
+
i += 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else
|
|
41
|
+
out += '[^/]*';
|
|
42
|
+
}
|
|
43
|
+
else if (c === '?')
|
|
44
|
+
out += '[^/]';
|
|
45
|
+
else if (c === '[' && p.indexOf(']', i + 1) > i + 1) {
|
|
46
|
+
const j = p.indexOf(']', i + 1);
|
|
47
|
+
const cls = p.slice(i + 1, j);
|
|
48
|
+
out += '[' + (cls[0] === '!' ? '^' + cls.slice(1) : cls).replace(/\\/g, '\\\\') + ']';
|
|
49
|
+
i = j;
|
|
50
|
+
}
|
|
51
|
+
else if (c === '{' && p.indexOf('}', i) > i) {
|
|
52
|
+
const j = p.indexOf('}', i);
|
|
53
|
+
out += '(?:' + p.slice(i + 1, j).split(',').map(globBody).join('|') + ')';
|
|
54
|
+
i = j;
|
|
55
|
+
}
|
|
56
|
+
else if (c === '\\' && i + 1 < p.length) {
|
|
57
|
+
out += escapeRe(p[i + 1]);
|
|
58
|
+
i++;
|
|
59
|
+
}
|
|
60
|
+
else
|
|
61
|
+
out += escapeRe(c);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&');
|
|
66
|
+
export function globToRegExp(glob) {
|
|
67
|
+
const anchored = glob.includes('/');
|
|
68
|
+
const p = glob.startsWith('/') ? glob.slice(1) : glob;
|
|
69
|
+
return new RegExp(anchored ? `^${globBody(p)}$` : `^(?:.*/)?${globBody(p)}$`);
|
|
70
|
+
}
|
|
71
|
+
export { globBody };
|
package/dist/version.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hullproof",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Hullproof Engine: deterministic, stack aware security checks and release gate for AI built apps.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "Rare Phronesis Limited",
|
|
7
|
+
"homepage": "https://github.com/jt247/hullproof-engine",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/jt247/hullproof-engine.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/jt247/hullproof-engine/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"bin": {
|
|
20
|
+
"hullproof": "dist/cli.js"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=22.18.0"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "node --test test/*.test.ts bench/*.test.ts",
|
|
27
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
28
|
+
"build": "node build.mjs",
|
|
29
|
+
"prepack": "node build.mjs",
|
|
30
|
+
"discover": "node src/cli.ts discover"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^26.6.5",
|
|
34
|
+
"typescript": "^7.0.2"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"src",
|
|
39
|
+
"checks",
|
|
40
|
+
"detectors",
|
|
41
|
+
"rules",
|
|
42
|
+
"schemas",
|
|
43
|
+
"packs",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"NOTICE",
|
|
46
|
+
"README.md"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"detect": {
|
|
3
|
+
"expected": [
|
|
4
|
+
"android",
|
|
5
|
+
"kotlin"
|
|
6
|
+
],
|
|
7
|
+
"found": [
|
|
8
|
+
"android",
|
|
9
|
+
"kotlin"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"engine": "0.5.0",
|
|
13
|
+
"measured": "2026-10-14",
|
|
14
|
+
"measurements": [
|
|
15
|
+
{
|
|
16
|
+
"blind_key": true,
|
|
17
|
+
"flaws": {
|
|
18
|
+
"false_findings": 0,
|
|
19
|
+
"found": 2,
|
|
20
|
+
"planted": 7
|
|
21
|
+
},
|
|
22
|
+
"kind": "synthetic",
|
|
23
|
+
"mapped": {
|
|
24
|
+
"expected": 3,
|
|
25
|
+
"extra": 0,
|
|
26
|
+
"matched": 3,
|
|
27
|
+
"what": "entry points"
|
|
28
|
+
},
|
|
29
|
+
"name": "test/fixtures/packs/android/fail and test/fixtures/packs/android/pass",
|
|
30
|
+
"source": "node bench/packs-measure.ts android (key kept outside the repository)",
|
|
31
|
+
"stability": {
|
|
32
|
+
"identical": true,
|
|
33
|
+
"runs": 3
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"notes": [
|
|
38
|
+
"Android app in Kotlin: seven planted configuration flaws and a fixed copy. Five of the seven are settled by checks that belong to the Pro edition. Written before the Android extractor existed."
|
|
39
|
+
],
|
|
40
|
+
"pack": "android",
|
|
41
|
+
"proof": [],
|
|
42
|
+
"schema": 1
|
|
43
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"id": "android",
|
|
4
|
+
"name": "Kotlin and Android",
|
|
5
|
+
"summary": "AndroidManifest.xml, the network security configuration and exported components read into the code map, with a check for secrets written into configuration files. Kotlin source is not read.",
|
|
6
|
+
"edition": "free",
|
|
7
|
+
"languages": [
|
|
8
|
+
"kotlin",
|
|
9
|
+
"java"
|
|
10
|
+
],
|
|
11
|
+
"frameworks": [
|
|
12
|
+
"android"
|
|
13
|
+
],
|
|
14
|
+
"level": "L2",
|
|
15
|
+
"limits": [
|
|
16
|
+
"Kotlin and Java source is not read, so what a handler does with an incoming intent is not in the map."
|
|
17
|
+
],
|
|
18
|
+
"parts": {
|
|
19
|
+
"detectors": [
|
|
20
|
+
"kotlin",
|
|
21
|
+
"android"
|
|
22
|
+
],
|
|
23
|
+
"extractors": [
|
|
24
|
+
{
|
|
25
|
+
"file": "src/map/mobile.ts"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"auth_patterns": [
|
|
29
|
+
{
|
|
30
|
+
"file": "src/map/mobile.ts"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"checks": [
|
|
34
|
+
{
|
|
35
|
+
"id": "check.map.mobile-secret-in-config"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"mapping": [
|
|
39
|
+
{
|
|
40
|
+
"file": "src/checks/map/config-checks.ts"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"file": "src/checks/map/entry-checks.ts"
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"fixture": {
|
|
48
|
+
"dir": "test/fixtures/packs/android/fail",
|
|
49
|
+
"fixed_dir": "test/fixtures/packs/android/pass",
|
|
50
|
+
"internal": true,
|
|
51
|
+
"answer_keys": [
|
|
52
|
+
"outside_repo"
|
|
53
|
+
],
|
|
54
|
+
"result": "packs/android/fixture-result.json"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"detect": {
|
|
3
|
+
"expected": [
|
|
4
|
+
"aspnet-core",
|
|
5
|
+
"csharp"
|
|
6
|
+
],
|
|
7
|
+
"found": [
|
|
8
|
+
"aspnet-core",
|
|
9
|
+
"csharp",
|
|
10
|
+
"entity-framework"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"engine": "0.5.0",
|
|
14
|
+
"measured": "2026-10-14",
|
|
15
|
+
"measurements": [
|
|
16
|
+
{
|
|
17
|
+
"blind_key": true,
|
|
18
|
+
"flaws": {
|
|
19
|
+
"false_findings": 0,
|
|
20
|
+
"found": 6,
|
|
21
|
+
"planted": 6
|
|
22
|
+
},
|
|
23
|
+
"kind": "synthetic",
|
|
24
|
+
"mapped": {
|
|
25
|
+
"expected": 11,
|
|
26
|
+
"extra": 0,
|
|
27
|
+
"matched": 11,
|
|
28
|
+
"what": "entry points"
|
|
29
|
+
},
|
|
30
|
+
"name": "test/fixtures/packs/dotnet/fail and test/fixtures/packs/dotnet/pass",
|
|
31
|
+
"source": "node bench/packs-measure.ts dotnet (key kept outside the repository)",
|
|
32
|
+
"stability": {
|
|
33
|
+
"identical": true,
|
|
34
|
+
"runs": 3
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"blind_key": true,
|
|
39
|
+
"flaws": null,
|
|
40
|
+
"kind": "third_party",
|
|
41
|
+
"mapped": {
|
|
42
|
+
"expected": 25,
|
|
43
|
+
"extra": 0,
|
|
44
|
+
"matched": 25,
|
|
45
|
+
"what": "entry points (real route registrations)"
|
|
46
|
+
},
|
|
47
|
+
"name": "realworld-apps/aspnetcore-realworld-example-app (seeded sample of its route registrations)",
|
|
48
|
+
"source": "bench/PHASE6C-MEASURED-2026-10-14.md, recall by bench/phase6c-sample.ts with seed 20261017, three byte identical ci runs",
|
|
49
|
+
"stability": {
|
|
50
|
+
"identical": true,
|
|
51
|
+
"runs": 3
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"notes": [
|
|
56
|
+
"ASP.NET Core 8 with controllers and minimal APIs: six planted flaws and a fixed copy. Written before the .NET extractor existed. Route token convention: [controller] is replaced by the lower case controller name.",
|
|
57
|
+
"2026-10-14: the first flaw also accepts check.map.read-by-id-without-owner, which was built in step 6c after these fixtures missed the flaw."
|
|
58
|
+
],
|
|
59
|
+
"pack": "dotnet",
|
|
60
|
+
"proof": [],
|
|
61
|
+
"schema": 1
|
|
62
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"id": "dotnet",
|
|
4
|
+
"name": ".NET and ASP.NET Core",
|
|
5
|
+
"summary": "ASP.NET Core controllers and minimal APIs with [Authorize], [AllowAnonymous], RequireAuthorization and a fallback policy: entry points, protections and checks.",
|
|
6
|
+
"edition": "free",
|
|
7
|
+
"languages": [
|
|
8
|
+
"csharp"
|
|
9
|
+
],
|
|
10
|
+
"frameworks": [
|
|
11
|
+
"aspnet-core"
|
|
12
|
+
],
|
|
13
|
+
"level": "L2",
|
|
14
|
+
"limits": [
|
|
15
|
+
"Conventional routing (MapControllerRoute) is not read. Planted flaws were not measured on a real application."
|
|
16
|
+
],
|
|
17
|
+
"parts": {
|
|
18
|
+
"detectors": [
|
|
19
|
+
"csharp",
|
|
20
|
+
"aspnet-core"
|
|
21
|
+
],
|
|
22
|
+
"extractors": [
|
|
23
|
+
{
|
|
24
|
+
"file": "src/map/dotnet.ts"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"file": "src/map/cblocks.ts"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"auth_patterns": [
|
|
31
|
+
{
|
|
32
|
+
"file": "src/map/light-profiles.ts"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"checks": [
|
|
36
|
+
{
|
|
37
|
+
"id": "check.map.admin-route-without-role"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "check.map.data-read-without-auth"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "check.map.mutating-route-without-auth"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "check.map.privileged-action-without-audit"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "check.map.privileged-field-from-request"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "check.map.redirect-from-request"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "check.map.request-url-fetched"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "check.map.webhook-verify-before-use"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "check.map.write-by-id-without-owner"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "check.map.read-by-id-without-owner"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "check.authz.owner-filter-on-id-read"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "check.authz.admin-role-check"
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"mapping": [
|
|
74
|
+
{
|
|
75
|
+
"file": "src/checks/map/entry-checks.ts"
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
"fixture": {
|
|
80
|
+
"dir": "test/fixtures/packs/dotnet/fail",
|
|
81
|
+
"fixed_dir": "test/fixtures/packs/dotnet/pass",
|
|
82
|
+
"internal": true,
|
|
83
|
+
"answer_keys": [
|
|
84
|
+
"outside_repo"
|
|
85
|
+
],
|
|
86
|
+
"result": "packs/dotnet/fixture-result.json"
|
|
87
|
+
}
|
|
88
|
+
}
|