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,52 @@
|
|
|
1
|
+
// Executable checks: the definition format (checks/*.json) and the result format (checks.json).
|
|
2
|
+
// JSON schemas: schemas/check.schema.json, schemas/check-results.schema.json. Documented in docs/INTERFACES.md.
|
|
3
|
+
import type { StackCategory } from './stack.ts';
|
|
4
|
+
import type { ResultWord } from './results.ts';
|
|
5
|
+
|
|
6
|
+
export type CheckOp =
|
|
7
|
+
| 'file_exists' | 'file_exists_for_each' | 'file_absent'
|
|
8
|
+
| 'regex_present' | 'regex_absent' | 'json_value'
|
|
9
|
+
| 'sql_every_table_has' | 'workflow_uses_pinned_sha' | 'workflow_permissions' | 'version_in_table'
|
|
10
|
+
| 'code'; // a check written as TypeScript, see CodeCheckDefinition
|
|
11
|
+
|
|
12
|
+
// One declarative check definition (checks/*.json). Data only, no code. The interpreter in src/checks/ runs it.
|
|
13
|
+
export interface CheckDefinition {
|
|
14
|
+
id: string; // "check.<area>.<name>", for example "check.supply.lockfile-committed"
|
|
15
|
+
sec_ids: string[];
|
|
16
|
+
// Runs only when every named stack category has an item whose id is listed, or any item when the value is "*".
|
|
17
|
+
applies_when: Partial<Record<StackCategory, string[] | '*'>>;
|
|
18
|
+
op: CheckOp;
|
|
19
|
+
params: Record<string, unknown>; // op specific, documented per op in docs/INTERFACES.md by the checks author
|
|
20
|
+
on_true: ResultWord;
|
|
21
|
+
on_false: ResultWord;
|
|
22
|
+
fixtures: { pass: string; fail: string }; // folders under test/fixtures/checks/
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// A check written as TypeScript when no declarative op can say it: op "code", no params, and module names the file in
|
|
26
|
+
// src/checks/code/ that exports it. The five code checks keep this metadata in their module (src/checks/types.ts CodeCheck).
|
|
27
|
+
export interface CodeCheckDefinition extends Omit<CheckDefinition, 'op' | 'params'> {
|
|
28
|
+
op: 'code';
|
|
29
|
+
module: string; // for example "src/checks/code/webhook-verify.ts"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CheckEvidence { file: string; line?: number; key?: string; note: string }
|
|
33
|
+
|
|
34
|
+
// The search that supports an absence result (same idea as absence_search in the findings record).
|
|
35
|
+
export interface AbsenceSearch { pattern: string; files_searched: number; file_globs: string[]; matches: number }
|
|
36
|
+
|
|
37
|
+
export interface CheckResult {
|
|
38
|
+
check_id: string;
|
|
39
|
+
sec_ids: string[];
|
|
40
|
+
result: ResultWord;
|
|
41
|
+
evidence: CheckEvidence[]; // sorted by file, then line
|
|
42
|
+
absence_search: AbsenceSearch | null;
|
|
43
|
+
message_key: string; // key into the engine's plain English text, normally the check id
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// checks.json
|
|
47
|
+
export interface CheckResultsFile {
|
|
48
|
+
schema_version: '1.0.0';
|
|
49
|
+
commit: string | null;
|
|
50
|
+
results: CheckResult[]; // sorted by check_id
|
|
51
|
+
not_run: { check_id: string; reason: string }[]; // sorted by check_id
|
|
52
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// codemap.json, written by `hullproof map` and by `scan`. JSON schema: schemas/codemap.schema.json. Design: docs/PHASE-2-DESIGN.md.
|
|
2
|
+
import type { Confidence } from './stack.ts';
|
|
3
|
+
|
|
4
|
+
export type EntryKind =
|
|
5
|
+
| 'http_route' | 'webhook' | 'server_action' | 'page' | 'ai_tool' | 'job' | 'graphql_resolver' | 'rpc'
|
|
6
|
+
| 'message_consumer' | 'websocket' | 'deep_link' | 'ipc';
|
|
7
|
+
|
|
8
|
+
export type ProtectionKind = 'session_read' | 'role_check' | 'owner_filter' | 'signature_verify' | 'rate_limit' | 'input_validation' | 'audit_log';
|
|
9
|
+
export type Scope = 'handler' | 'wrapper' | 'router' | 'middleware' | 'global';
|
|
10
|
+
|
|
11
|
+
export interface Protection {
|
|
12
|
+
kind: ProtectionKind;
|
|
13
|
+
scope: Scope;
|
|
14
|
+
file: string;
|
|
15
|
+
line: number;
|
|
16
|
+
via: string; // the call, decorator or name that matched
|
|
17
|
+
enforced: boolean | null; // true: a failed check stops the request; false: it carries on; null: cannot tell
|
|
18
|
+
after_parse?: boolean; // signature_verify only: the body is parsed before the signature is checked
|
|
19
|
+
confidence: Confidence;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type InputSource = 'body' | 'query' | 'params' | 'headers' | 'cookies' | 'form' | 'file';
|
|
23
|
+
export interface InputRead { source: InputSource; line: number }
|
|
24
|
+
|
|
25
|
+
export type StoreKind = 'sql' | 'prisma' | 'drizzle' | 'mongo' | 'supabase' | 'firebase' | 'redis' | 'orm' | 'filesystem' | 'object_storage';
|
|
26
|
+
export interface DataTouch {
|
|
27
|
+
store: StoreKind;
|
|
28
|
+
op: 'read' | 'write' | 'delete' | 'unknown';
|
|
29
|
+
target: string | null; // table or model name when known
|
|
30
|
+
line: number;
|
|
31
|
+
by_id: boolean; // filters on an id column with a value from the request
|
|
32
|
+
limit_from_request: boolean; // a limit or page size comes from the request
|
|
33
|
+
request_derived: boolean; // the query text or its arguments contain a request derived value
|
|
34
|
+
owner_scoped: boolean; // the where clause names an owner, user, tenant or account column and the value comes from the session
|
|
35
|
+
raw_sql: boolean;
|
|
36
|
+
sets: string[]; // privileged columns (role, plan, is_admin ...) a write sets from a request value, sorted
|
|
37
|
+
mass_assign: boolean; // a whole request object goes into a create or update
|
|
38
|
+
confidence: Confidence;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type OutboundKind = 'http' | 'ai_model' | 'email' | 'sms' | 'payment' | 'process' | 'redirect' | 'queue';
|
|
42
|
+
export interface Outbound {
|
|
43
|
+
kind: OutboundKind;
|
|
44
|
+
line: number;
|
|
45
|
+
target: string; // the target expression, clipped to 120 characters
|
|
46
|
+
request_derived: boolean;
|
|
47
|
+
guarded: boolean; // an allowlist or host check in the same function
|
|
48
|
+
confidence: Confidence;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface EntryPoint {
|
|
52
|
+
id: string;
|
|
53
|
+
kind: EntryKind;
|
|
54
|
+
framework: string;
|
|
55
|
+
file: string;
|
|
56
|
+
line: number;
|
|
57
|
+
end_line: number;
|
|
58
|
+
method: string | null;
|
|
59
|
+
path: string | null;
|
|
60
|
+
name: string | null;
|
|
61
|
+
handler: { file: string; line: number } | null; // where the handler body is, when it is registered by reference
|
|
62
|
+
confidence: Confidence;
|
|
63
|
+
test_code: boolean;
|
|
64
|
+
tags: string[]; // sorted: admin, auth_endpoint, public, state_changing, upload, webhook
|
|
65
|
+
protections: Protection[];
|
|
66
|
+
reads: InputRead[];
|
|
67
|
+
data: DataTouch[];
|
|
68
|
+
outbound: Outbound[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface Gate {
|
|
72
|
+
id: string;
|
|
73
|
+
kind: 'middleware' | 'guard' | 'router_dependency' | 'global_pipe' | 'mount';
|
|
74
|
+
framework: string;
|
|
75
|
+
file: string;
|
|
76
|
+
line: number;
|
|
77
|
+
paths: string[]; // path prefixes covered; ['*'] for everything
|
|
78
|
+
methods: string[]; // [] for all
|
|
79
|
+
protections: Protection[];
|
|
80
|
+
confidence: Confidence;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type CoverageStatus = 'mapped' | 'partial' | 'not_mapped';
|
|
84
|
+
export interface Coverage {
|
|
85
|
+
language: string;
|
|
86
|
+
files: number;
|
|
87
|
+
extractor: string | null;
|
|
88
|
+
status: CoverageStatus;
|
|
89
|
+
entry_points: number;
|
|
90
|
+
note: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface SupabasePolicy { name: string; op: string; roles: string[]; using: string | null; check: string | null; line: number; permissive: boolean }
|
|
94
|
+
export interface SupabaseGrant { role: string; privileges: string[]; columns: string[] | null; line: number }
|
|
95
|
+
export interface SupabaseTable {
|
|
96
|
+
name: string; file: string; line: number; rls_enabled: boolean | null; columns: string[];
|
|
97
|
+
policies: SupabasePolicy[]; grants: SupabaseGrant[];
|
|
98
|
+
}
|
|
99
|
+
export interface FirebaseRule {
|
|
100
|
+
service: 'firestore' | 'storage' | 'realtime';
|
|
101
|
+
file: string; line: number; path: string; ops: string[]; condition: string;
|
|
102
|
+
uses_auth: boolean; owner_bound: boolean; always_true: boolean; owner_wildcard: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---- native mobile and Rust configuration (Phase 6f)
|
|
106
|
+
export interface ConfigSecret { file: string; line: number; key: string; where: 'android manifest' | 'android strings' | 'ios plist' }
|
|
107
|
+
export interface AndroidFilter { actions: string[]; categories: string[]; schemes: string[]; hosts: string[]; paths: string[] }
|
|
108
|
+
export interface AndroidComponent {
|
|
109
|
+
kind: 'activity' | 'service' | 'receiver' | 'provider'; name: string; line: number;
|
|
110
|
+
exported: boolean; exported_explicit: boolean; permission: string | null; launcher: boolean; filters: AndroidFilter[];
|
|
111
|
+
}
|
|
112
|
+
export interface AndroidManifest {
|
|
113
|
+
file: string; package: string | null; line: number; debuggable: boolean | null; allow_backup: boolean | null; uses_cleartext_traffic: boolean | null;
|
|
114
|
+
network_security_config: string | null; target_sdk: number | null; permissions: string[]; components: AndroidComponent[];
|
|
115
|
+
}
|
|
116
|
+
export interface NetworkSecurity { file: string; line: number; cleartext_base: boolean | null; cleartext_domains: string[]; trusts_user_certs: boolean; pinned_domains: number }
|
|
117
|
+
export interface AndroidConfig { manifests: AndroidManifest[]; network_security: NetworkSecurity[] }
|
|
118
|
+
export interface IosPlist { file: string; ats_arbitrary_loads: boolean | null; ats_line: number | null; url_schemes: string[]; associated_domains: string[]; usage_descriptions: string[] }
|
|
119
|
+
export interface IosConfig { plists: IosPlist[] }
|
|
120
|
+
export interface CargoDependency { name: string; source: 'registry' | 'git' | 'path'; version: string | null; line: number }
|
|
121
|
+
export interface CargoManifest {
|
|
122
|
+
file: string; line: number; name: string | null; edition: string | null; dependencies: CargoDependency[];
|
|
123
|
+
release_debug: boolean | null; release_overflow_checks: boolean | null; unsafe_code_lint: string | null; has_lock: boolean; binary: boolean;
|
|
124
|
+
}
|
|
125
|
+
export interface CargoConfig { manifests: CargoManifest[] }
|
|
126
|
+
|
|
127
|
+
export interface AuthHelperCandidate { name: string; entry_points: number; file: string; line: number }
|
|
128
|
+
export interface LocalVocabRecord { auth_helpers: string[]; role_helpers: string[] }
|
|
129
|
+
|
|
130
|
+
export interface CodeMap {
|
|
131
|
+
schema_version: '1.0.0';
|
|
132
|
+
engine_version: string;
|
|
133
|
+
commit: string | null;
|
|
134
|
+
coverage: Coverage[];
|
|
135
|
+
entry_points: EntryPoint[];
|
|
136
|
+
gates: Gate[];
|
|
137
|
+
config: {
|
|
138
|
+
supabase: { tables: SupabaseTable[] } | null; firebase: { rules: FirebaseRule[] } | null; android: AndroidConfig | null; ios: IosConfig | null; cargo: CargoConfig | null;
|
|
139
|
+
secrets: ConfigSecret[]; local_vocab: LocalVocabRecord; auth_helper_candidates: AuthHelperCandidate[];
|
|
140
|
+
};
|
|
141
|
+
counts: { entry_points: number; by_kind: Record<string, number>; by_framework: Record<string, number> };
|
|
142
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
|
|
6
|
+
export type Severity = 'BLOCKER' | 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
|
|
7
|
+
export type FailClass = 'finding' | 'missing_control' | 'hardening_note';
|
|
8
|
+
export type RouteStateWord = 'NEEDS DASHBOARD' | 'NEEDS BUILD' | 'NEEDS DYNAMIC TEST' | 'ASK OWNER' | 'ATTESTATION' | 'UNKNOWN';
|
|
9
|
+
|
|
10
|
+
export interface FileLineNote { file: string; line: number; note: string }
|
|
11
|
+
|
|
12
|
+
export interface Boundary { principal: string; input: string; intended_control: string; crossed: string; affected: string; result: string }
|
|
13
|
+
export interface TraceStep extends FileLineNote { kind: 'entrypoint' | 'handoff' | 'sink' }
|
|
14
|
+
|
|
15
|
+
// New in 1.1.0. A fail record carrying this may be class "finding" without a trace: the rule hit is the evidence.
|
|
16
|
+
export interface MachineEvidence {
|
|
17
|
+
tool: string; // "semgrep", "gitleaks", "trivy", "hullproof-check" and so on
|
|
18
|
+
rule_id: string; // scanner rule id or engine check id
|
|
19
|
+
tool_version: string;
|
|
20
|
+
config_sha256: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FailRecord {
|
|
24
|
+
fingerprint: string; // "<SEC id|GAP|OBS>:<file or ->:<slug>"
|
|
25
|
+
kind: 'fail';
|
|
26
|
+
sec_ids: string[];
|
|
27
|
+
title: string;
|
|
28
|
+
class: FailClass;
|
|
29
|
+
gap_in_standard: boolean;
|
|
30
|
+
severity: Severity;
|
|
31
|
+
confidence: 'VERIFIED' | 'SUSPECTED';
|
|
32
|
+
demonstrated: boolean;
|
|
33
|
+
root_cause: string;
|
|
34
|
+
raised_by: string[];
|
|
35
|
+
impact_statement: string;
|
|
36
|
+
remediation: { strategy: string; confirm: string };
|
|
37
|
+
evidence: FileLineNote[];
|
|
38
|
+
boundary?: Boundary;
|
|
39
|
+
trace?: TraceStep[];
|
|
40
|
+
absence_search?: { command: string; files_searched: number; count: number; practice: 'absent' | 'present_unsafe' };
|
|
41
|
+
preventing_layer?: FileLineNote[];
|
|
42
|
+
paths_enumerated?: { count: number; method: string };
|
|
43
|
+
expiry_condition?: string;
|
|
44
|
+
owner_evidence?: { file: string; hash: string; hash_kind: 'git_blob' | 'sha256'; date: string; commit: string; note: string }[];
|
|
45
|
+
lowering?: { from: Severity; to: Severity; evidence: FileLineNote[] };
|
|
46
|
+
verified_by?: { agent: 'hullproof-finding-verifier'; mode: 'finding'; outcome: 'UPHELD' | 'ADJUSTED' | 'INSUFFICIENT'; own_severity?: Severity; own_class?: FailClass; call: number };
|
|
47
|
+
machine_evidence?: MachineEvidence;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface RoutedRecord {
|
|
51
|
+
fingerprint: string;
|
|
52
|
+
kind: 'routed';
|
|
53
|
+
sec_ids: string[];
|
|
54
|
+
title: string;
|
|
55
|
+
state: RouteStateWord;
|
|
56
|
+
reason: string;
|
|
57
|
+
owner_action: string;
|
|
58
|
+
check_card: string | null;
|
|
59
|
+
raised_by: string[];
|
|
60
|
+
boundary: Boundary;
|
|
61
|
+
trace?: TraceStep[];
|
|
62
|
+
verified_by?: { agent: 'hullproof-finding-verifier'; mode: 'handoff'; outcome: 'UPHELD' | 'ADJUSTED' | 'INSUFFICIENT'; call: number };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface RefutedRecord {
|
|
66
|
+
fingerprint: string;
|
|
67
|
+
kind: 'refuted';
|
|
68
|
+
sec_ids: string[];
|
|
69
|
+
title: string;
|
|
70
|
+
claim: string;
|
|
71
|
+
reason: string;
|
|
72
|
+
preventing_layer: FileLineNote[];
|
|
73
|
+
raised_by: string[];
|
|
74
|
+
verified_by: { agent: 'hullproof-finding-verifier'; mode: 'finding' | 'handoff'; outcome: 'REFUTED'; call: number };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ObservationRecord {
|
|
78
|
+
fingerprint: string;
|
|
79
|
+
kind: 'observation';
|
|
80
|
+
sec_ids: string[];
|
|
81
|
+
title: string;
|
|
82
|
+
where: FileLineNote[];
|
|
83
|
+
note: string;
|
|
84
|
+
raised_by: string[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type FindingRecord = FailRecord | RoutedRecord | RefutedRecord | ObservationRecord;
|
|
88
|
+
|
|
89
|
+
export interface FindingsReport {
|
|
90
|
+
commit: string;
|
|
91
|
+
tree: string;
|
|
92
|
+
tree_clean: boolean;
|
|
93
|
+
stage: 'LAUNCH' | 'GROWTH' | 'SCALE';
|
|
94
|
+
profile: 'quick' | 'standard' | 'deep';
|
|
95
|
+
scope: 'full' | 'partial';
|
|
96
|
+
scope_note?: string;
|
|
97
|
+
run_status: 'complete' | 'incomplete';
|
|
98
|
+
incomplete_reason?: 'budget_exhausted' | 'agent_failure' | 'owner_stopped' | 'deterministic_only'; // deterministic_only: schema 1.1.0, engine run, no verifier by design
|
|
99
|
+
agent_calls: number;
|
|
100
|
+
budget: number | null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface FindingsFile {
|
|
104
|
+
schema_version: '1.0.0' | '1.1.0';
|
|
105
|
+
report: FindingsReport;
|
|
106
|
+
records: FindingRecord[]; // sorted by fingerprint
|
|
107
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Types of the hunt files under <out>/<commit>/hunt/. Documented in docs/INTERFACES.md. The confirmation engine that reads them is
|
|
2
|
+
// open; the step that calls a model and writes the answers is not part of this repository.
|
|
3
|
+
export type Json = null | boolean | number | string | Json[] | { [k: string]: Json };
|
|
4
|
+
|
|
5
|
+
export interface LeadStep { file: string; line: number; note: string }
|
|
6
|
+
|
|
7
|
+
// A condition of the map query language (src/confirm/query.ts).
|
|
8
|
+
export type Cond =
|
|
9
|
+
| { all: Cond[] } | { any: Cond[] } | { not: Cond }
|
|
10
|
+
| { field: string; op: 'eq' | 'ne' | 'in' | 'contains' | 'startsWith' | 'exists' | 'gt' | 'lt'; value?: Json }
|
|
11
|
+
| { some: string; where?: Cond } | { none: string; where?: Cond };
|
|
12
|
+
export interface Query { where: Cond }
|
|
13
|
+
|
|
14
|
+
export type EvidenceProposal =
|
|
15
|
+
| { kind: 'map_query'; query: Query }
|
|
16
|
+
| { kind: 'semgrep_rule'; rule_yaml: string; control_code: string; control_extension: string }
|
|
17
|
+
| { kind: 'generated_test' }
|
|
18
|
+
| { kind: 'none'; rejected?: string };
|
|
19
|
+
|
|
20
|
+
export interface Lead {
|
|
21
|
+
lead_id: string;
|
|
22
|
+
entry_point: string;
|
|
23
|
+
requirement_ids: string[]; // sorted, unique
|
|
24
|
+
claim: string;
|
|
25
|
+
path: LeadStep[]; // entry point first, sink last
|
|
26
|
+
quoted_code: string; // the code at the sink, as it stands in the file
|
|
27
|
+
evidence_proposal: EvidenceProposal;
|
|
28
|
+
claim_truncated?: true; // the model's claim was longer than the limit and was shortened
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// One saved answer: one call for one entry point in one run. Written as soon as it arrives.
|
|
32
|
+
export interface HuntAnswer {
|
|
33
|
+
schema_version: '1.0.0';
|
|
34
|
+
run: number;
|
|
35
|
+
entry_id: string;
|
|
36
|
+
status: 'complete' | 'incomplete';
|
|
37
|
+
stop: string | null; // why an incomplete call stopped: refusal, safety, rate_limit, timeout, length, ...
|
|
38
|
+
detail: string | null;
|
|
39
|
+
provider: string;
|
|
40
|
+
model: string;
|
|
41
|
+
context_sha256: string;
|
|
42
|
+
truncated: boolean;
|
|
43
|
+
usage: { input_tokens: number; output_tokens: number };
|
|
44
|
+
output: Json; // the model's JSON as received; null for an incomplete call
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface HuntPlan {
|
|
48
|
+
schema_version: '1.0.0';
|
|
49
|
+
commit: string | null;
|
|
50
|
+
runs: number;
|
|
51
|
+
entries: string[]; // entry point ids in hunting order
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type Confirmation =
|
|
55
|
+
| { status: 'confirmed'; method: 'map_query' | 'semgrep_rule' | 'sandbox_test'; run: number; detail: string; tool: string; rule_id: string; tool_version: string; config_sha256: string }
|
|
56
|
+
| { status: 'not_confirmed'; method: 'map_query' | 'semgrep_rule' | 'generated_test' | 'none'; detail: string; reason?: 'unknown_protection'; helpers?: string[] }
|
|
57
|
+
| { status: 'not_run'; method: 'semgrep_rule'; detail: string };
|
|
58
|
+
|
|
59
|
+
export interface LeadOut {
|
|
60
|
+
key: string;
|
|
61
|
+
claim_truncated?: true;
|
|
62
|
+
entry_point: string;
|
|
63
|
+
requirement_ids: string[];
|
|
64
|
+
claim: string;
|
|
65
|
+
path: LeadStep[];
|
|
66
|
+
sink: { file: string; line: number };
|
|
67
|
+
runs_raised: number;
|
|
68
|
+
runs: number[];
|
|
69
|
+
confirmation: Confirmation;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface HuntResult {
|
|
73
|
+
schema_version: '1.0.0';
|
|
74
|
+
engine_version: string;
|
|
75
|
+
commit: string | null;
|
|
76
|
+
runs: number;
|
|
77
|
+
entries_hunted: number;
|
|
78
|
+
calls: { complete: number; incomplete: number; missing: number };
|
|
79
|
+
incomplete: { entry_id: string; run: number; stop: string; detail: string }[];
|
|
80
|
+
malformed: { count: number; items: { run: number; entry_id: string; lead_id: string; reason: string }[] };
|
|
81
|
+
hallucinations: { count: number; items: { run: number; entry_id: string; lead_id: string; reason: string }[] };
|
|
82
|
+
confirmed: LeadOut[];
|
|
83
|
+
leads: LeadOut[]; // everything not confirmed, most runs first
|
|
84
|
+
usage: { calls: number; input_tokens: number; output_tokens: number };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface HuntSynthesis {
|
|
88
|
+
schema_version: '1.0.0';
|
|
89
|
+
status: 'complete' | 'incomplete';
|
|
90
|
+
stop: string | null;
|
|
91
|
+
detail: string | null;
|
|
92
|
+
provider: string;
|
|
93
|
+
model: string;
|
|
94
|
+
summary: string;
|
|
95
|
+
explanations: { key: string; text: string }[];
|
|
96
|
+
usage: { input_tokens: number; output_tokens: number };
|
|
97
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Shared types for every engine module. Import with `import type { ... } from '../types/index.ts'`.
|
|
2
|
+
export type * from './stack.ts';
|
|
3
|
+
export type * from './scan.ts';
|
|
4
|
+
export type * from './check.ts';
|
|
5
|
+
export type * from './results.ts';
|
|
6
|
+
export type * from './findings.ts';
|
|
7
|
+
export { STACK_CATEGORIES } from './stack.ts';
|
|
8
|
+
export type * from './codemap.ts';
|
|
9
|
+
export type * from './hunt.ts';
|
|
10
|
+
export type * from './prove.ts';
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Types of the proof sandbox (Phase 4): the test spec language, the recipe that starts an app, and the files under <out>/<commit>/prove/.
|
|
2
|
+
// Documented in docs/INTERFACES.md and docs/PHASE-4-DESIGN.md.
|
|
3
|
+
import type { Json } from './hunt.ts';
|
|
4
|
+
|
|
5
|
+
export type Method = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
6
|
+
export type Phase = 'setup' | 'control' | 'attack';
|
|
7
|
+
|
|
8
|
+
export type Assertion =
|
|
9
|
+
| { kind: 'status'; op: 'eq' | 'ne' | 'in'; value: number | number[] }
|
|
10
|
+
| { kind: 'json'; path: string; op: 'eq' | 'ne' | 'contains' | 'exists' | 'absent'; value?: string | number | boolean | null }
|
|
11
|
+
| { kind: 'text'; op: 'contains' | 'absent'; value: string }
|
|
12
|
+
| { kind: 'header'; name: string; op: 'eq' | 'contains' | 'exists' | 'absent'; value?: string };
|
|
13
|
+
|
|
14
|
+
export interface Capture { name: string; from: 'json' | 'header' | 'status'; path?: string }
|
|
15
|
+
|
|
16
|
+
export interface SpecStep {
|
|
17
|
+
id: string; phase: Phase; actor: string; method: Method; path: string;
|
|
18
|
+
headers?: Record<string, string>; body?: { [k: string]: Json } | null; captures?: Capture[]; assert: Assertion[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Spec {
|
|
22
|
+
schema_version: '1.0.0';
|
|
23
|
+
spec_id: string; title: string; claim: string;
|
|
24
|
+
lead_key: string | null; // the hunt lead this spec tests, when it came from one
|
|
25
|
+
target: { entry_point: string; file: string; line: number };
|
|
26
|
+
requirement_ids: string[];
|
|
27
|
+
actors: Record<string, { headers?: Record<string, string> }>;
|
|
28
|
+
steps: SpecStep[];
|
|
29
|
+
expect: { outcome: 'flaw_present' }; // the attack steps' assertions describe the flaw; they all hold when it is there
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface StepRecord {
|
|
33
|
+
id: string; phase: Phase; actor: string;
|
|
34
|
+
error: string | null; // the request could not be made (connection, timeout, bad variable)
|
|
35
|
+
request: { method: Method; path: string; headers: Record<string, string>; body: Json };
|
|
36
|
+
response: { status: number; headers: Record<string, string>; json: Json | undefined; text: string | undefined; truncated: boolean } | null;
|
|
37
|
+
captures: Record<string, Json>;
|
|
38
|
+
assertions: { ok: boolean; desc: string }[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type RunOutcome = 'proven' | 'not_proven' | 'invalid' | 'error';
|
|
42
|
+
export type SpecOutcome = RunOutcome | 'unstable' | 'not_provable';
|
|
43
|
+
|
|
44
|
+
// One run of one spec, as stored under prove/runs/<spec_id>/run-N.json. The hash covers everything but itself.
|
|
45
|
+
export interface RunRecord {
|
|
46
|
+
schema_version: '1.0.0'; spec_id: string; spec_sha256: string; run: number;
|
|
47
|
+
outcome: RunOutcome; detail: string; steps: StepRecord[]; transcript_sha256: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ProveRunSummary { run: number; outcome: RunOutcome; transcript_sha256: string; detail: string }
|
|
51
|
+
export interface ProveSpecResult {
|
|
52
|
+
spec_id: string; spec_sha256: string; lead_key: string | null; title: string; claim: string;
|
|
53
|
+
entry_point: string; requirement_ids: string[]; sink: { file: string; line: number };
|
|
54
|
+
outcome: SpecOutcome; reason: string; runs: ProveRunSummary[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type NotProvableCode =
|
|
58
|
+
| 'no_package_json' | 'unsupported_stack' | 'hosted_service' | 'unsupported_datastore' | 'no_lockfile' | 'needs_install_scripts'
|
|
59
|
+
| 'no_start_command' | 'port_unknown' | 'db_path_hardcoded' | 'no_seed' | 'no_runtime' | 'refused_unsafe_backend' | 'build_failed' | 'start_failed' | 'health_failed' | 'time_limit' | 'too_large';
|
|
60
|
+
export interface NotProvable { code: NotProvableCode; reason: string }
|
|
61
|
+
|
|
62
|
+
export interface ProveMeta { schema_version: '1.0.0'; commit: string | null; runner: string; runs_required: number; recipe_sha256: string | null; not_provable: NotProvable | null; left_behind: number; copy_excluded: string[] }
|
|
63
|
+
export interface ProveResult {
|
|
64
|
+
schema_version: '1.0.0'; engine_version: string; commit: string | null; runner: string; runs_required: number;
|
|
65
|
+
recipe_sha256: string | null; not_provable: NotProvable | null;
|
|
66
|
+
specs: ProveSpecResult[];
|
|
67
|
+
dropped: { file: string; reason: string }[]; // spec files that failed validation and were not run
|
|
68
|
+
left_behind: number; // containers, images or networks still there after teardown (0 when clean)
|
|
69
|
+
copy_excluded: string[]; // what was left out of the copy that went into the sandbox
|
|
70
|
+
counts: { specs: number; proven: number; not_proven: number; invalid: number; unstable: number; error: number; not_provable: number; dropped: number };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface Recipe {
|
|
74
|
+
schema_version: '1.0.0'; stack: 'node-express' | 'python-flask'; base_image: string;
|
|
75
|
+
install_scripts?: boolean; // true only when the owner passed --allow-install-scripts: install scripts run (a Python package may build from source) in the build container
|
|
76
|
+
install: string[] | null; // argv of the build phase install, null when nothing is installed
|
|
77
|
+
vendored_modules: boolean; // node_modules of the repository is part of the copy (no install, no registry); always false for Python
|
|
78
|
+
seed: string | null; start: string; port: number; port_from_env: boolean; health_path: string;
|
|
79
|
+
data_store: { kind: 'none' } | { kind: 'sqlite'; env: string[] } | { kind: 'postgres'; migrations: string[]; seed_sql: string | null };
|
|
80
|
+
env: Record<string, string>; // dummy values only; {DATA} stands for the writable data folder
|
|
81
|
+
notes: string[];
|
|
82
|
+
}
|
|
83
|
+
export type RecipeResult = { ok: true; recipe: Recipe } | ({ ok: false } & NotProvable);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// results.json: the per requirement result list the kit gate (hullproof-gate.mjs, loadResults) already reads.
|
|
2
|
+
|
|
3
|
+
export type RouteState = 'NEEDS DASHBOARD' | 'NEEDS BUILD' | 'NEEDS DYNAMIC TEST' | 'ASK OWNER' | 'ATTESTATION' | 'UNKNOWN';
|
|
4
|
+
|
|
5
|
+
// The result words of STANDARD.md. "PASS (static)" settles only repo authority controls; the gate enforces that.
|
|
6
|
+
export type ResultWord = 'FAIL' | 'PASS (static)' | 'NOT APPLICABLE' | `NOT ASSESSED: ${RouteState}`;
|
|
7
|
+
|
|
8
|
+
export interface ResultRow {
|
|
9
|
+
id: string; // SEC-XXX-000
|
|
10
|
+
result: ResultWord;
|
|
11
|
+
notes: string; // NOT APPLICABLE needs a reason here; plain text, no secret values
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// results.json is a JSON array of ResultRow sorted by id.
|
|
15
|
+
export type ResultsFile = ResultRow[];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Scanner adapters and their normalised output. JSON schemas: schemas/scan-hits.schema.json, schemas/scan-meta.schema.json.
|
|
2
|
+
// Written by the adapters in src/scanners/. Documented in docs/INTERFACES.md.
|
|
3
|
+
import type { Stack } from './stack.ts';
|
|
4
|
+
|
|
5
|
+
export type ScannerId = 'semgrep' | 'osv-scanner' | 'gitleaks' | 'trivy' | 'checkov';
|
|
6
|
+
|
|
7
|
+
// One normalised scanner hit. No secret value, no tool message text, no absolute path, no timing.
|
|
8
|
+
export interface Hit {
|
|
9
|
+
tool: ScannerId;
|
|
10
|
+
rule_id: string; // the tool's own rule or check id, as the tool prints it
|
|
11
|
+
file: string; // repository relative POSIX path
|
|
12
|
+
line: number; // 1 based; 0 only when the tool gives no line (for example a lockfile level advisory)
|
|
13
|
+
sec_ids: string[]; // requirement IDs from the rule metadata or engine/maps/<tool>.json; [] means unmapped
|
|
14
|
+
severity_hint: string | null; // the tool's own severity word, kept for the report; never used by the gate
|
|
15
|
+
message_key: string; // key into the engine's plain English text, normally `${tool}:${rule_id}`
|
|
16
|
+
noise?: string; // secret hits only: why the value is an example, not a live secret (src/scanners/secret-noise.ts); absent when it must hold
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// scan/<tool>.json
|
|
20
|
+
export interface HitsFile {
|
|
21
|
+
schema_version: '1.0.0';
|
|
22
|
+
tool: ScannerId;
|
|
23
|
+
hits: Hit[]; // sorted by file, line, tool, rule_id
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// scan/<tool>.meta.json. The reproducibility record.
|
|
27
|
+
export interface ToolMeta {
|
|
28
|
+
schema_version: '1.0.0';
|
|
29
|
+
tool: ScannerId;
|
|
30
|
+
status: 'ran' | 'skipped: not installed' | 'skipped: not applicable' | 'skipped: network not allowed' | 'failed';
|
|
31
|
+
version: string | null;
|
|
32
|
+
tested_version_range: string;
|
|
33
|
+
version_tested: boolean; // false when the installed version is outside tested_version_range
|
|
34
|
+
argv: string[]; // exact command, no shell; [] when skipped
|
|
35
|
+
config_sha256: string | null;
|
|
36
|
+
commit: string | null;
|
|
37
|
+
files_scanned: number | null;
|
|
38
|
+
files_tracked: number | null;
|
|
39
|
+
exit_code: number | null;
|
|
40
|
+
db_date: string | null; // OSV Scanner advisory data date, else null
|
|
41
|
+
not_assessed_sec_ids: string[]; // IDs that stay NOT ASSESSED because this tool did not run
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface RunContext {
|
|
45
|
+
repo: string; // absolute path of the repository being scanned
|
|
46
|
+
out: string; // absolute path of the output folder
|
|
47
|
+
stack: Stack;
|
|
48
|
+
allowNetwork: boolean;
|
|
49
|
+
edition: 'free' | 'pro';
|
|
50
|
+
kit?: string | null; // the kit folder the scan resolved; absent or null: the path order in src/mapping/controls.ts
|
|
51
|
+
history?: boolean; // set by the scan step after its git configuration check; lets `gitleaks git` run
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ScannerAdapter {
|
|
55
|
+
id: ScannerId;
|
|
56
|
+
versionCommand: string[];
|
|
57
|
+
testedVersions: string;
|
|
58
|
+
needsNetwork: boolean;
|
|
59
|
+
applies(stack: Stack): boolean;
|
|
60
|
+
command(ctx: RunContext): string[];
|
|
61
|
+
configFiles(ctx: RunContext): string[];
|
|
62
|
+
parse(stdout: string, ctx: RunContext): Hit[];
|
|
63
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// stack.json, written by `hullproof discover`. JSON schema: schemas/stack.schema.json. Documented in docs/INTERFACES.md.
|
|
2
|
+
|
|
3
|
+
export type Confidence = 'high' | 'medium' | 'low';
|
|
4
|
+
|
|
5
|
+
export const STACK_CATEGORIES = [
|
|
6
|
+
'languages', 'runtimes', 'frameworks', 'package_managers', 'data_stores', 'auth', 'payments', 'ai',
|
|
7
|
+
'hosting', 'iac', 'containers', 'ci', 'mobile',
|
|
8
|
+
] as const;
|
|
9
|
+
export type StackCategory = typeof STACK_CATEGORIES[number];
|
|
10
|
+
|
|
11
|
+
// One proof that an item is present. Exactly one of line or key is set.
|
|
12
|
+
// line: 1 based line in file. key: a JSON key path such as "dependencies.express", or "(present)" when the file itself
|
|
13
|
+
// is the signal (vercel.json, Dockerfile), or "(extension)" when the file extension is the signal.
|
|
14
|
+
export interface Evidence {
|
|
15
|
+
file: string; // repository relative POSIX path
|
|
16
|
+
line?: number;
|
|
17
|
+
key?: string;
|
|
18
|
+
rule: string; // detector rule id, for example "frameworks.express.dep.npm"
|
|
19
|
+
confidence: Confidence;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface StackItem {
|
|
23
|
+
id: string;
|
|
24
|
+
confidence: Confidence; // the best confidence among all evidence
|
|
25
|
+
evidence: Evidence[]; // at most 5, best confidence first, then file, then line
|
|
26
|
+
evidence_count: number; // all evidence found, including entries not listed
|
|
27
|
+
versions?: string[]; // declared versions or ranges from manifests, as written
|
|
28
|
+
file_count?: number; // languages only: files with this language's extensions
|
|
29
|
+
kind?: string; // hosting only: "platform" or "cloud"
|
|
30
|
+
clouds?: string[]; // iac only: providers found in those files
|
|
31
|
+
lockfiles?: string[]; // package_managers only
|
|
32
|
+
manifests?: string[]; // package_managers only
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface Skipped { path: string; reason: 'gitignore' | 'dependency folder' | 'build output' | 'tool folder' | 'symlink' | 'unchanged kit files' }
|
|
36
|
+
export interface Unknown { file: string; note: string }
|
|
37
|
+
|
|
38
|
+
export interface Stack {
|
|
39
|
+
schema_version: '1.0.0';
|
|
40
|
+
engine_version: string;
|
|
41
|
+
detectors_sha256: string; // sha256 over the sorted detector files
|
|
42
|
+
commit: string | null; // HEAD commit read from .git without running git; null when not a git repository
|
|
43
|
+
confirmed_by_user: boolean; // false until `--confirm` exists (later phase)
|
|
44
|
+
files: { listed: number; scanned_for_imports: number };
|
|
45
|
+
limits: { max_files: number; max_bytes_per_file: number; file_cap_reached: boolean; truncated_files: string[] };
|
|
46
|
+
skipped: Skipped[];
|
|
47
|
+
languages: StackItem[];
|
|
48
|
+
runtimes: StackItem[];
|
|
49
|
+
frameworks: StackItem[];
|
|
50
|
+
package_managers: StackItem[];
|
|
51
|
+
data_stores: StackItem[];
|
|
52
|
+
auth: StackItem[];
|
|
53
|
+
payments: StackItem[];
|
|
54
|
+
ai: StackItem[];
|
|
55
|
+
hosting: StackItem[];
|
|
56
|
+
iac: StackItem[];
|
|
57
|
+
containers: StackItem[];
|
|
58
|
+
ci: StackItem[];
|
|
59
|
+
mobile: StackItem[];
|
|
60
|
+
unknown: Unknown[];
|
|
61
|
+
}
|