wingfoil 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +334 -0
- package/dist/cli/error.d.ts +11 -0
- package/dist/cli/error.d.ts.map +1 -0
- package/dist/cli/error.js +27 -0
- package/dist/cli/error.js.map +1 -0
- package/dist/cli/exit.d.ts +19 -0
- package/dist/cli/exit.d.ts.map +1 -0
- package/dist/cli/exit.js +14 -0
- package/dist/cli/exit.js.map +1 -0
- package/dist/cli/index.d.ts +28 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +36 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init-command.d.ts +34 -0
- package/dist/cli/init-command.d.ts.map +1 -0
- package/dist/cli/init-command.js +100 -0
- package/dist/cli/init-command.js.map +1 -0
- package/dist/cli/mcp-command.d.ts +24 -0
- package/dist/cli/mcp-command.d.ts.map +1 -0
- package/dist/cli/mcp-command.js +42 -0
- package/dist/cli/mcp-command.js.map +1 -0
- package/dist/cli/output.d.ts +13 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +28 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/program.d.ts +12 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +279 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/registrar.d.ts +69 -0
- package/dist/cli/registrar.d.ts.map +1 -0
- package/dist/cli/registrar.js +82 -0
- package/dist/cli/registrar.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +45 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/approval-authority.d.ts +83 -0
- package/dist/core/approval-authority.d.ts.map +1 -0
- package/dist/core/approval-authority.js +112 -0
- package/dist/core/approval-authority.js.map +1 -0
- package/dist/core/builtin-asset.d.ts +71 -0
- package/dist/core/builtin-asset.d.ts.map +1 -0
- package/dist/core/builtin-asset.js +137 -0
- package/dist/core/builtin-asset.js.map +1 -0
- package/dist/core/builtin-integrity.d.ts +32 -0
- package/dist/core/builtin-integrity.d.ts.map +1 -0
- package/dist/core/builtin-integrity.js +148 -0
- package/dist/core/builtin-integrity.js.map +1 -0
- package/dist/core/confinement.d.ts +57 -0
- package/dist/core/confinement.d.ts.map +1 -0
- package/dist/core/confinement.js +108 -0
- package/dist/core/confinement.js.map +1 -0
- package/dist/core/context.d.ts +187 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +192 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/directive-assign.d.ts +131 -0
- package/dist/core/directive-assign.d.ts.map +1 -0
- package/dist/core/directive-assign.js +352 -0
- package/dist/core/directive-assign.js.map +1 -0
- package/dist/core/directives-list.d.ts +80 -0
- package/dist/core/directives-list.d.ts.map +1 -0
- package/dist/core/directives-list.js +148 -0
- package/dist/core/directives-list.js.map +1 -0
- package/dist/core/exit-code.d.ts +85 -0
- package/dist/core/exit-code.d.ts.map +1 -0
- package/dist/core/exit-code.js +163 -0
- package/dist/core/exit-code.js.map +1 -0
- package/dist/core/git-identity.d.ts +32 -0
- package/dist/core/git-identity.d.ts.map +1 -0
- package/dist/core/git-identity.js +72 -0
- package/dist/core/git-identity.js.map +1 -0
- package/dist/core/index.d.ts +388 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +1331 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/init.d.ts +78 -0
- package/dist/core/init.d.ts.map +1 -0
- package/dist/core/init.js +167 -0
- package/dist/core/init.js.map +1 -0
- package/dist/core/loaders.d.ts +174 -0
- package/dist/core/loaders.d.ts.map +1 -0
- package/dist/core/loaders.js +364 -0
- package/dist/core/loaders.js.map +1 -0
- package/dist/core/memory-add-type.d.ts +43 -0
- package/dist/core/memory-add-type.d.ts.map +1 -0
- package/dist/core/memory-add-type.js +151 -0
- package/dist/core/memory-add-type.js.map +1 -0
- package/dist/core/memory-transition.d.ts +111 -0
- package/dist/core/memory-transition.d.ts.map +1 -0
- package/dist/core/memory-transition.js +269 -0
- package/dist/core/memory-transition.js.map +1 -0
- package/dist/core/registry.d.ts +189 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +66 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/relevance.d.ts +86 -0
- package/dist/core/relevance.d.ts.map +1 -0
- package/dist/core/relevance.js +258 -0
- package/dist/core/relevance.js.map +1 -0
- package/dist/core/require-reason.d.ts +27 -0
- package/dist/core/require-reason.d.ts.map +1 -0
- package/dist/core/require-reason.js +95 -0
- package/dist/core/require-reason.js.map +1 -0
- package/dist/core/types.d.ts +45 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +22 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/usage-error.d.ts +20 -0
- package/dist/core/usage-error.d.ts.map +1 -0
- package/dist/core/usage-error.js +29 -0
- package/dist/core/usage-error.js.map +1 -0
- package/dist/core/write-guard.d.ts +99 -0
- package/dist/core/write-guard.d.ts.map +1 -0
- package/dist/core/write-guard.js +247 -0
- package/dist/core/write-guard.js.map +1 -0
- package/dist/directives/create.d.ts +47 -0
- package/dist/directives/create.d.ts.map +1 -0
- package/dist/directives/create.js +82 -0
- package/dist/directives/create.js.map +1 -0
- package/dist/directives/index.d.ts +20 -0
- package/dist/directives/index.d.ts.map +1 -0
- package/dist/directives/index.js +32 -0
- package/dist/directives/index.js.map +1 -0
- package/dist/directives/roles-edit.d.ts +53 -0
- package/dist/directives/roles-edit.d.ts.map +1 -0
- package/dist/directives/roles-edit.js +286 -0
- package/dist/directives/roles-edit.js.map +1 -0
- package/dist/directives/schema.d.ts +65 -0
- package/dist/directives/schema.d.ts.map +1 -0
- package/dist/directives/schema.js +68 -0
- package/dist/directives/schema.js.map +1 -0
- package/dist/dna/edit.d.ts +61 -0
- package/dist/dna/edit.d.ts.map +1 -0
- package/dist/dna/edit.js +429 -0
- package/dist/dna/edit.js.map +1 -0
- package/dist/dna/index.d.ts +24 -0
- package/dist/dna/index.d.ts.map +1 -0
- package/dist/dna/index.js +59 -0
- package/dist/dna/index.js.map +1 -0
- package/dist/dna/mutate.d.ts +74 -0
- package/dist/dna/mutate.d.ts.map +1 -0
- package/dist/dna/mutate.js +318 -0
- package/dist/dna/mutate.js.map +1 -0
- package/dist/dna/path.d.ts +175 -0
- package/dist/dna/path.d.ts.map +1 -0
- package/dist/dna/path.js +289 -0
- package/dist/dna/path.js.map +1 -0
- package/dist/dna/roles.d.ts +57 -0
- package/dist/dna/roles.d.ts.map +1 -0
- package/dist/dna/roles.js +50 -0
- package/dist/dna/roles.js.map +1 -0
- package/dist/dna/schema.d.ts +172 -0
- package/dist/dna/schema.d.ts.map +1 -0
- package/dist/dna/schema.js +182 -0
- package/dist/dna/schema.js.map +1 -0
- package/dist/dna/set.d.ts +145 -0
- package/dist/dna/set.d.ts.map +1 -0
- package/dist/dna/set.js +428 -0
- package/dist/dna/set.js.map +1 -0
- package/dist/mcp/dna-resource.d.ts +16 -0
- package/dist/mcp/dna-resource.d.ts.map +1 -0
- package/dist/mcp/dna-resource.js +56 -0
- package/dist/mcp/dna-resource.js.map +1 -0
- package/dist/mcp/index.d.ts +49 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +60 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/memory-resource.d.ts +17 -0
- package/dist/mcp/memory-resource.d.ts.map +1 -0
- package/dist/mcp/memory-resource.js +133 -0
- package/dist/mcp/memory-resource.js.map +1 -0
- package/dist/mcp/prompt.d.ts +48 -0
- package/dist/mcp/prompt.d.ts.map +1 -0
- package/dist/mcp/prompt.js +176 -0
- package/dist/mcp/prompt.js.map +1 -0
- package/dist/mcp/read-only.d.ts +42 -0
- package/dist/mcp/read-only.d.ts.map +1 -0
- package/dist/mcp/read-only.js +93 -0
- package/dist/mcp/read-only.js.map +1 -0
- package/dist/mcp/registrar.d.ts +54 -0
- package/dist/mcp/registrar.d.ts.map +1 -0
- package/dist/mcp/registrar.js +72 -0
- package/dist/mcp/registrar.js.map +1 -0
- package/dist/mcp/server.d.ts +58 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +64 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/workflow-resource.d.ts +17 -0
- package/dist/mcp/workflow-resource.d.ts.map +1 -0
- package/dist/mcp/workflow-resource.js +63 -0
- package/dist/mcp/workflow-resource.js.map +1 -0
- package/dist/memory/add.d.ts +50 -0
- package/dist/memory/add.d.ts.map +1 -0
- package/dist/memory/add.js +131 -0
- package/dist/memory/add.js.map +1 -0
- package/dist/memory/audit.d.ts +177 -0
- package/dist/memory/audit.d.ts.map +1 -0
- package/dist/memory/audit.js +287 -0
- package/dist/memory/audit.js.map +1 -0
- package/dist/memory/commit-message.d.ts +146 -0
- package/dist/memory/commit-message.d.ts.map +1 -0
- package/dist/memory/commit-message.js +203 -0
- package/dist/memory/commit-message.js.map +1 -0
- package/dist/memory/entry.d.ts +56 -0
- package/dist/memory/entry.d.ts.map +1 -0
- package/dist/memory/entry.js +28 -0
- package/dist/memory/entry.js.map +1 -0
- package/dist/memory/frontmatter-edit.d.ts +68 -0
- package/dist/memory/frontmatter-edit.d.ts.map +1 -0
- package/dist/memory/frontmatter-edit.js +333 -0
- package/dist/memory/frontmatter-edit.js.map +1 -0
- package/dist/memory/git-log.d.ts +32 -0
- package/dist/memory/git-log.d.ts.map +1 -0
- package/dist/memory/git-log.js +100 -0
- package/dist/memory/git-log.js.map +1 -0
- package/dist/memory/history.d.ts +142 -0
- package/dist/memory/history.d.ts.map +1 -0
- package/dist/memory/history.js +250 -0
- package/dist/memory/history.js.map +1 -0
- package/dist/memory/index.d.ts +58 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +107 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/query.d.ts +150 -0
- package/dist/memory/query.d.ts.map +1 -0
- package/dist/memory/query.js +307 -0
- package/dist/memory/query.js.map +1 -0
- package/dist/memory/reject.d.ts +11 -0
- package/dist/memory/reject.d.ts.map +1 -0
- package/dist/memory/reject.js +32 -0
- package/dist/memory/reject.js.map +1 -0
- package/dist/memory/schema.d.ts +104 -0
- package/dist/memory/schema.d.ts.map +1 -0
- package/dist/memory/schema.js +161 -0
- package/dist/memory/schema.js.map +1 -0
- package/dist/memory/state-machine.d.ts +166 -0
- package/dist/memory/state-machine.d.ts.map +1 -0
- package/dist/memory/state-machine.js +431 -0
- package/dist/memory/state-machine.js.map +1 -0
- package/dist/memory/submit.d.ts +11 -0
- package/dist/memory/submit.d.ts.map +1 -0
- package/dist/memory/submit.js +44 -0
- package/dist/memory/submit.js.map +1 -0
- package/dist/storage/builtin-directives.d.ts +54 -0
- package/dist/storage/builtin-directives.d.ts.map +1 -0
- package/dist/storage/builtin-directives.js +152 -0
- package/dist/storage/builtin-directives.js.map +1 -0
- package/dist/storage/commit.d.ts +92 -0
- package/dist/storage/commit.d.ts.map +1 -0
- package/dist/storage/commit.js +201 -0
- package/dist/storage/commit.js.map +1 -0
- package/dist/storage/confinement.d.ts +93 -0
- package/dist/storage/confinement.d.ts.map +1 -0
- package/dist/storage/confinement.js +164 -0
- package/dist/storage/confinement.js.map +1 -0
- package/dist/storage/document.d.ts +27 -0
- package/dist/storage/document.d.ts.map +1 -0
- package/dist/storage/document.js +52 -0
- package/dist/storage/document.js.map +1 -0
- package/dist/storage/errors.d.ts +36 -0
- package/dist/storage/errors.d.ts.map +1 -0
- package/dist/storage/errors.js +46 -0
- package/dist/storage/errors.js.map +1 -0
- package/dist/storage/frontmatter.d.ts +27 -0
- package/dist/storage/frontmatter.d.ts.map +1 -0
- package/dist/storage/frontmatter.js +37 -0
- package/dist/storage/frontmatter.js.map +1 -0
- package/dist/storage/git-root.d.ts +17 -0
- package/dist/storage/git-root.d.ts.map +1 -0
- package/dist/storage/git-root.js +49 -0
- package/dist/storage/git-root.js.map +1 -0
- package/dist/storage/index.d.ts +29 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +66 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/init-state.d.ts +9 -0
- package/dist/storage/init-state.d.ts.map +1 -0
- package/dist/storage/init-state.js +24 -0
- package/dist/storage/init-state.js.map +1 -0
- package/dist/storage/layout.d.ts +51 -0
- package/dist/storage/layout.d.ts.map +1 -0
- package/dist/storage/layout.js +88 -0
- package/dist/storage/layout.js.map +1 -0
- package/dist/storage/memory-path.d.ts +72 -0
- package/dist/storage/memory-path.d.ts.map +1 -0
- package/dist/storage/memory-path.js +127 -0
- package/dist/storage/memory-path.js.map +1 -0
- package/dist/storage/snapshot.d.ts +16 -0
- package/dist/storage/snapshot.d.ts.map +1 -0
- package/dist/storage/snapshot.js +72 -0
- package/dist/storage/snapshot.js.map +1 -0
- package/dist/storage/templates.d.ts +88 -0
- package/dist/storage/templates.d.ts.map +1 -0
- package/dist/storage/templates.js +422 -0
- package/dist/storage/templates.js.map +1 -0
- package/dist/validation/error-mapper.d.ts +16 -0
- package/dist/validation/error-mapper.d.ts.map +1 -0
- package/dist/validation/error-mapper.js +18 -0
- package/dist/validation/error-mapper.js.map +1 -0
- package/dist/validation/errors.d.ts +69 -0
- package/dist/validation/errors.d.ts.map +1 -0
- package/dist/validation/errors.js +69 -0
- package/dist/validation/errors.js.map +1 -0
- package/dist/validation/id.d.ts +16 -0
- package/dist/validation/id.d.ts.map +1 -0
- package/dist/validation/id.js +177 -0
- package/dist/validation/id.js.map +1 -0
- package/dist/validation/index.d.ts +18 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +37 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/secret-scan.d.ts +129 -0
- package/dist/validation/secret-scan.d.ts.map +1 -0
- package/dist/validation/secret-scan.js +367 -0
- package/dist/validation/secret-scan.js.map +1 -0
- package/dist/validation/two-pass.d.ts +24 -0
- package/dist/validation/two-pass.d.ts.map +1 -0
- package/dist/validation/two-pass.js +24 -0
- package/dist/validation/two-pass.js.map +1 -0
- package/dist/validation/warning.d.ts +31 -0
- package/dist/validation/warning.d.ts.map +1 -0
- package/dist/validation/warning.js +81 -0
- package/dist/validation/warning.js.map +1 -0
- package/dist/validation/yaml.d.ts +9 -0
- package/dist/validation/yaml.d.ts.map +1 -0
- package/dist/validation/yaml.js +30 -0
- package/dist/validation/yaml.js.map +1 -0
- package/dist/workflow/index.d.ts +10 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +16 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/schema.d.ts +89 -0
- package/dist/workflow/schema.d.ts.map +1 -0
- package/dist/workflow/schema.js +73 -0
- package/dist/workflow/schema.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_IGNORE_FILE = exports.SCAN_SURFACE_ROOTS = exports.SECRET_PATTERNS = void 0;
|
|
4
|
+
exports.scanText = scanText;
|
|
5
|
+
exports.isBinaryContent = isBinaryContent;
|
|
6
|
+
exports.loadIgnoreGlobs = loadIgnoreGlobs;
|
|
7
|
+
exports.matchesIgnoreGlob = matchesIgnoreGlob;
|
|
8
|
+
exports.scanProjectSurface = scanProjectSurface;
|
|
9
|
+
/**
|
|
10
|
+
* Secret/credential hygiene scan (task-043-secret-credential-hygiene, REQ-SEC-08), built to
|
|
11
|
+
* `spec-007-secret-hygiene-patterns` — the single canonical pattern set and scan procedure spec-007
|
|
12
|
+
* requires so no future call site (a commit-time gate, `task-044`'s `init` integrity check, a future
|
|
13
|
+
* `wingfoil audit`) re-implements its own regex list and lets the patterns drift.
|
|
14
|
+
*
|
|
15
|
+
* `SECRET_PATTERNS` reproduces spec-007 §2's ten named rules; each JS `RegExp` below carries a
|
|
16
|
+
* comment quoting the exact spec-007 YAML `regex:` string it implements. Two of spec-007's patterns
|
|
17
|
+
* use the PCRE inline mode-modifier syntax `(?i)`/`(?im)` in their YAML text — JS `RegExp` has no
|
|
18
|
+
* such inline syntax (`new RegExp('(?i)...')` throws `SyntaxError`) — so that flag is moved to the
|
|
19
|
+
* equivalent JS `i` flag instead. This is a mechanical syntax translation, not a semantic rewrite:
|
|
20
|
+
* the matched shape is unchanged, so it satisfies spec-007's Consequences-section instruction to
|
|
21
|
+
* "reproduce the pattern table in §2 verbatim (or import it as data)" rather than hand-roll
|
|
22
|
+
* alternative regexes.
|
|
23
|
+
*/
|
|
24
|
+
const child_process_1 = require("child_process");
|
|
25
|
+
const fs_1 = require("fs");
|
|
26
|
+
const path_1 = require("path");
|
|
27
|
+
/**
|
|
28
|
+
* The canonical secret-hygiene pattern set (spec-007 §2), in the fixed declaration order every scan
|
|
29
|
+
* must evaluate patterns in (determinism, REQ-SYS-07, spec-007 §4 step 3). Adding, removing, or
|
|
30
|
+
* re-classifying a pattern is a revision to spec-007's §2 table, not a call-site code change.
|
|
31
|
+
*/
|
|
32
|
+
exports.SECRET_PATTERNS = [
|
|
33
|
+
{
|
|
34
|
+
id: 'private-key-pem',
|
|
35
|
+
description: 'PEM-encoded private key header',
|
|
36
|
+
severity: 'block',
|
|
37
|
+
// spec-007 §2: '-----BEGIN (RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----'
|
|
38
|
+
regex: /-----BEGIN (RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'generic-api-key-assignment',
|
|
42
|
+
description: 'Variable assignment that looks like an API key/secret/token',
|
|
43
|
+
severity: 'block',
|
|
44
|
+
// spec-007 §2 (case-insensitive per its `(?i)` marker):
|
|
45
|
+
// '(api[_-]?key|secret|token|passwd|password)\s*[:=]\s*["\']?[A-Za-z0-9_\-\/+=]{16,}["\']?'
|
|
46
|
+
regex: /(api[_-]?key|secret|token|passwd|password)\s*[:=]\s*["']?[A-Za-z0-9_\-/+=]{16,}["']?/i,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'aws-access-key-id',
|
|
50
|
+
description: 'AWS access key ID shape',
|
|
51
|
+
severity: 'block',
|
|
52
|
+
// spec-007 §2: '\b(AKIA|ASIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASCA)[0-9A-Z]{16}\b'
|
|
53
|
+
regex: /\b(AKIA|ASIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASCA)[0-9A-Z]{16}\b/,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'aws-secret-access-key',
|
|
57
|
+
description: "AWS secret access key assignment (40-char base64-ish, near 'aws' or 'secret')",
|
|
58
|
+
severity: 'block',
|
|
59
|
+
// spec-007 §2 (case-insensitive per its `(?i)` marker):
|
|
60
|
+
// 'aws.{0,20}(secret|key).{0,5}[:=]\s*["\']?[A-Za-z0-9\/+=]{40}["\']?'
|
|
61
|
+
regex: /aws.{0,20}(secret|key).{0,5}[:=]\s*["']?[A-Za-z0-9/+=]{40}["']?/i,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'gcp-service-account-key',
|
|
65
|
+
description: 'GCP service-account JSON key fragment',
|
|
66
|
+
severity: 'block',
|
|
67
|
+
// spec-007 §2: '"type"\s*:\s*"service_account"|"private_key_id"\s*:\s*"[0-9a-f]{40}"'
|
|
68
|
+
regex: /"type"\s*:\s*"service_account"|"private_key_id"\s*:\s*"[0-9a-f]{40}"/,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'github-token',
|
|
72
|
+
description: 'GitHub personal access / app / fine-grained token',
|
|
73
|
+
severity: 'block',
|
|
74
|
+
// spec-007 §2: '\b(ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{36,}\b'
|
|
75
|
+
regex: /\b(ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{36,}\b/,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'slack-token',
|
|
79
|
+
description: 'Slack bot/user/app token',
|
|
80
|
+
severity: 'block',
|
|
81
|
+
// spec-007 §2: '\bxox[baprs]-[A-Za-z0-9-]{10,}\b'
|
|
82
|
+
regex: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'jwt-like',
|
|
86
|
+
description: 'JSON Web Token shape (header.payload.signature, base64url segments)',
|
|
87
|
+
// promoted warn → block by dl-036-secret-scan-warn-severity-vs-req-sec-08 (spec-007 §2 amended)
|
|
88
|
+
severity: 'block',
|
|
89
|
+
// spec-007 §2: '\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\b'
|
|
90
|
+
regex: /\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\b/,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'generic-high-entropy-string',
|
|
94
|
+
description: 'Long contiguous base64/hex-alphabet token assigned to a suspicious key name',
|
|
95
|
+
severity: 'warn',
|
|
96
|
+
// spec-007 §2 (case-insensitive per its `(?i)` marker):
|
|
97
|
+
// '(auth|credential|bearer)\s*[:=]\s*["\']?[A-Za-z0-9_\-\/+=]{24,}["\']?'
|
|
98
|
+
regex: /(auth|credential|bearer)\s*[:=]\s*["']?[A-Za-z0-9_\-/+=]{24,}["']?/i,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 'dotenv-style-secret-line',
|
|
102
|
+
description: '.env-style KEY=VALUE line where KEY names a credential',
|
|
103
|
+
// promoted warn → block by dl-036-secret-scan-warn-severity-vs-req-sec-08 (spec-007 §2 amended)
|
|
104
|
+
severity: 'block',
|
|
105
|
+
// spec-007 §2 (case-insensitive per its `(?im)` marker; the `m` is a no-op here because the
|
|
106
|
+
// scan procedure already evaluates one line at a time, so `^`/`$` anchor to that line either way):
|
|
107
|
+
// '^[A-Z0-9_]*(SECRET|TOKEN|PASSWORD|API_KEY|PRIVATE_KEY)[A-Z0-9_]*\s*=\s*\S+'
|
|
108
|
+
regex: /^[A-Z0-9_]*(SECRET|TOKEN|PASSWORD|API_KEY|PRIVATE_KEY)[A-Z0-9_]*\s*=\s*\S+/i,
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
const FENCE_DELIMITER_RE = /^`{3,}/;
|
|
112
|
+
const FENCE_EXEMPT_MARKERS = new Set(['<!-- example -->', '<!-- placeholder -->']);
|
|
113
|
+
/** Literal placeholder values spec-007 §3 exempts outright (case-insensitive, whole-value match). */
|
|
114
|
+
const PLACEHOLDER_LITERALS = new Set(['REDACTED', 'PLACEHOLDER', 'EXAMPLE', 'XXXXXXXXXXXXXXXX']);
|
|
115
|
+
/** True for a value that is entirely `x`/`X`/`*` repeats, or one of spec-007 §3's literal strings. */
|
|
116
|
+
function isPlaceholderValue(value) {
|
|
117
|
+
if (value.length === 0)
|
|
118
|
+
return false;
|
|
119
|
+
if (/^[xX*]+$/.test(value))
|
|
120
|
+
return true;
|
|
121
|
+
return PLACEHOLDER_LITERALS.has(value.toUpperCase());
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Best-effort extraction of the "value" side of a `key: value` / `key=value` match, for the
|
|
125
|
+
* placeholder-value exclusion (spec-007 §3). Takes everything after the match's last `:`/`=` (the
|
|
126
|
+
* separator every value-shaped pattern in §2 requires), trims whitespace, and strips one layer of
|
|
127
|
+
* surrounding quotes. Falls back to the whole match when no separator is present (vendor-prefixed
|
|
128
|
+
* shapes like `aws-access-key-id`/`github-token` have no separate value to isolate — the placeholder
|
|
129
|
+
* exclusion legitimately does not apply to those; the fenced-example / ignore-path exclusions do).
|
|
130
|
+
*/
|
|
131
|
+
function extractCandidateValue(matchText) {
|
|
132
|
+
const lastEquals = matchText.lastIndexOf('=');
|
|
133
|
+
const lastColon = matchText.lastIndexOf(':');
|
|
134
|
+
const sepIndex = Math.max(lastEquals, lastColon);
|
|
135
|
+
const raw = sepIndex >= 0 ? matchText.slice(sepIndex + 1) : matchText;
|
|
136
|
+
return raw.trim().replace(/^["']/, '').replace(/["']$/, '');
|
|
137
|
+
}
|
|
138
|
+
/** Every match of `pattern.regex` on a single `line`, as unexempt {@link SecretFinding}s. */
|
|
139
|
+
function matchLine(line, pattern, lineNumber, filePath) {
|
|
140
|
+
const flags = pattern.regex.flags.includes('g') ? pattern.regex.flags : `${pattern.regex.flags}g`;
|
|
141
|
+
const global = new RegExp(pattern.regex.source, flags);
|
|
142
|
+
const findings = [];
|
|
143
|
+
let match;
|
|
144
|
+
while ((match = global.exec(line)) !== null) {
|
|
145
|
+
findings.push({
|
|
146
|
+
patternId: pattern.id,
|
|
147
|
+
severity: pattern.severity,
|
|
148
|
+
file: filePath,
|
|
149
|
+
line: lineNumber,
|
|
150
|
+
column: match.index + 1,
|
|
151
|
+
excerpt: match[0],
|
|
152
|
+
});
|
|
153
|
+
if (match[0].length === 0)
|
|
154
|
+
global.lastIndex += 1; // guard a zero-length match's infinite loop
|
|
155
|
+
}
|
|
156
|
+
return findings;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Scan one file's already-decoded text content (spec-007 §4): evaluate {@link SECRET_PATTERNS} in
|
|
160
|
+
* declared order against every line, apply the §3 exclusions, and classify each surviving finding by
|
|
161
|
+
* severity. This is the seam a future commit-time gate or `task-044`'s `init` integrity check calls
|
|
162
|
+
* per file-about-to-be-written/committed (spec-007 §4 step 5).
|
|
163
|
+
*/
|
|
164
|
+
function scanText(content, filePath, options = {}) {
|
|
165
|
+
const lines = content.split(/\r\n|\r|\n/);
|
|
166
|
+
const blocking = [];
|
|
167
|
+
const warnings = [];
|
|
168
|
+
const info = [];
|
|
169
|
+
let inFence = false;
|
|
170
|
+
let fenceExempt = false;
|
|
171
|
+
for (let i = 0; i < lines.length; i++) {
|
|
172
|
+
const line = lines[i] ?? '';
|
|
173
|
+
if (FENCE_DELIMITER_RE.test(line.trim())) {
|
|
174
|
+
if (!inFence) {
|
|
175
|
+
const prevLine = i > 0 ? (lines[i - 1] ?? '').trim().toLowerCase() : '';
|
|
176
|
+
fenceExempt = FENCE_EXEMPT_MARKERS.has(prevLine);
|
|
177
|
+
inFence = true;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
inFence = false;
|
|
181
|
+
fenceExempt = false;
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
for (const pattern of exports.SECRET_PATTERNS) {
|
|
186
|
+
for (const finding of matchLine(line, pattern, i + 1, filePath)) {
|
|
187
|
+
const valueExempt = isPlaceholderValue(extractCandidateValue(finding.excerpt));
|
|
188
|
+
if (fenceExempt || valueExempt || options.pathIgnored) {
|
|
189
|
+
const exemptReason = fenceExempt
|
|
190
|
+
? 'fenced-example'
|
|
191
|
+
: valueExempt
|
|
192
|
+
? 'placeholder-value'
|
|
193
|
+
: 'ignore-path';
|
|
194
|
+
info.push({ ...finding, exemptReason });
|
|
195
|
+
}
|
|
196
|
+
else if (finding.severity === 'block') {
|
|
197
|
+
blocking.push(finding);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
warnings.push(finding);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { blocking, warnings, info, filesScanned: 1 };
|
|
206
|
+
}
|
|
207
|
+
/** Concatenate {@link ScanResult}s (file iteration order — deterministic when the caller's is). */
|
|
208
|
+
function mergeScanResults(results) {
|
|
209
|
+
return {
|
|
210
|
+
blocking: results.flatMap((r) => r.blocking),
|
|
211
|
+
warnings: results.flatMap((r) => r.warnings),
|
|
212
|
+
info: results.flatMap((r) => r.info),
|
|
213
|
+
filesScanned: results.reduce((total, r) => total + r.filesScanned, 0),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/** Null-byte sniff on the first 8KB (spec-007 §1) — binary files are out of scan scope. */
|
|
217
|
+
function isBinaryContent(buf) {
|
|
218
|
+
const head = buf.subarray(0, 8192);
|
|
219
|
+
return head.includes(0);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Default scan-surface roots (spec-007 §1): `.wingfoil/` once `init` exists, plus the current
|
|
223
|
+
* self-hosted analog (`docs/self/.wingfoil/`, `docs/self/docs/04_memory/`) "applied by analogy until
|
|
224
|
+
* the tool-managed root exists". A root with no indexed files contributes nothing, so
|
|
225
|
+
* {@link scanProjectSurface} is safe to call before `init` has ever run.
|
|
226
|
+
*/
|
|
227
|
+
exports.SCAN_SURFACE_ROOTS = ['.wingfoil', 'docs/self/.wingfoil', 'docs/self/docs/04_memory'];
|
|
228
|
+
/** Default path (root-relative) of the `security-ignore` glob list (spec-007 §3). */
|
|
229
|
+
exports.DEFAULT_IGNORE_FILE = '.wingfoil/security-ignore';
|
|
230
|
+
/** `git ls-files -s` mode of a gitlink (submodule commit) — not a blob, nothing to read. */
|
|
231
|
+
const GITLINK_MODE = '160000';
|
|
232
|
+
/** One `git ls-files -s -z` record: mode, object id, stage, then a tab and the raw path. */
|
|
233
|
+
const INDEX_RECORD_RE = /^(\d+) ([0-9a-f]+) \d\t([\s\S]+)$/;
|
|
234
|
+
/**
|
|
235
|
+
* List every indexed (tracked or staged, spec-007 §1) blob under `surfaceRoots`, in git's path order.
|
|
236
|
+
* `-z` keeps paths unquoted; gitlinks are skipped. An unmerged path contributes one entry per conflict
|
|
237
|
+
* stage — each is content that could be committed. A surface root absent from the index yields no
|
|
238
|
+
* entries (git accepts an unmatched pathspec), so there is no on-disk existence check: the index is
|
|
239
|
+
* the only source. Mirrors `src/storage/commit.ts`'s `execFileSync('git', ['-C', root, ...])` style.
|
|
240
|
+
*/
|
|
241
|
+
function listIndexedBlobs(root, surfaceRoots) {
|
|
242
|
+
if (surfaceRoots.length === 0)
|
|
243
|
+
return [];
|
|
244
|
+
const out = (0, child_process_1.execFileSync)('git', ['-C', root, 'ls-files', '-s', '-z', '--', ...surfaceRoots], {
|
|
245
|
+
encoding: 'utf-8',
|
|
246
|
+
env: process.env,
|
|
247
|
+
});
|
|
248
|
+
const entries = [];
|
|
249
|
+
for (const record of out.split('\0')) {
|
|
250
|
+
// `<mode> <object id> <stage>\t<path>`; the trailing empty record after the last NUL never matches
|
|
251
|
+
const match = INDEX_RECORD_RE.exec(record);
|
|
252
|
+
if (match === null || match[1] === GITLINK_MODE)
|
|
253
|
+
continue;
|
|
254
|
+
entries.push({ path: match[3], blob: match[2] });
|
|
255
|
+
}
|
|
256
|
+
return entries;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Read every entry's staged bytes from the object store in ONE `git cat-file --batch` call, preserving
|
|
260
|
+
* `entries` order. Each response is `<id> blob <size>\n<size bytes>\n`; any other header (e.g.
|
|
261
|
+
* `<id> missing`) means the index names an object the repository does not have, which is corruption,
|
|
262
|
+
* not a scan result — it throws.
|
|
263
|
+
*/
|
|
264
|
+
function readIndexedBlobs(root, entries) {
|
|
265
|
+
if (entries.length === 0)
|
|
266
|
+
return [];
|
|
267
|
+
const out = (0, child_process_1.execFileSync)('git', ['-C', root, 'cat-file', '--batch'], {
|
|
268
|
+
input: `${entries.map((entry) => entry.blob).join('\n')}\n`,
|
|
269
|
+
env: process.env,
|
|
270
|
+
maxBuffer: Number.POSITIVE_INFINITY,
|
|
271
|
+
});
|
|
272
|
+
let offset = 0;
|
|
273
|
+
return entries.map(({ path, blob }) => {
|
|
274
|
+
const headerEnd = out.indexOf(0x0a, offset);
|
|
275
|
+
const header = out.subarray(offset, headerEnd).toString('utf-8');
|
|
276
|
+
const match = /^([0-9a-f]+) blob (\d+)$/.exec(header);
|
|
277
|
+
if (match === null || match[1] !== blob) {
|
|
278
|
+
throw new Error(`git cat-file could not read the indexed blob of ${path}: "${header}"`);
|
|
279
|
+
}
|
|
280
|
+
const start = headerEnd + 1;
|
|
281
|
+
const end = start + Number(match[2]);
|
|
282
|
+
offset = end + 1; // skip the LF that terminates each object's content
|
|
283
|
+
return { path, content: out.subarray(start, end) };
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Parse a `security-ignore` file (spec-007 §3): one glob per line, git-attribute-style — blank lines
|
|
288
|
+
* and lines starting with `#` are ignored. Returns `[]` when `ignoreFilePath` does not exist (no
|
|
289
|
+
* ignore list configured is not an error).
|
|
290
|
+
*/
|
|
291
|
+
function loadIgnoreGlobs(ignoreFilePath) {
|
|
292
|
+
if (!(0, fs_1.existsSync)(ignoreFilePath))
|
|
293
|
+
return [];
|
|
294
|
+
return (0, fs_1.readFileSync)(ignoreFilePath, 'utf-8')
|
|
295
|
+
.split(/\r\n|\r|\n/)
|
|
296
|
+
.map((line) => line.trim())
|
|
297
|
+
.filter((line) => line.length > 0 && !line.startsWith('#'));
|
|
298
|
+
}
|
|
299
|
+
/** Escape one non-wildcard character for safe insertion into a glob-derived `RegExp` source. */
|
|
300
|
+
function escapeGlobLiteral(ch) {
|
|
301
|
+
return /[.*+?^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
|
|
302
|
+
}
|
|
303
|
+
/** Compile one `security-ignore` glob line (`*`, `**`, `?`) to a `RegExp` matching a full root-relative path. */
|
|
304
|
+
function globToRegExp(glob) {
|
|
305
|
+
let source = '^';
|
|
306
|
+
for (let i = 0; i < glob.length; i++) {
|
|
307
|
+
const ch = glob[i];
|
|
308
|
+
if (ch === '*') {
|
|
309
|
+
if (glob[i + 1] === '*') {
|
|
310
|
+
source += '.*';
|
|
311
|
+
i += 1;
|
|
312
|
+
if (glob[i + 1] === '/')
|
|
313
|
+
i += 1; // `**/` also swallows the following separator
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
source += '[^/]*';
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else if (ch === '?') {
|
|
320
|
+
source += '[^/]';
|
|
321
|
+
}
|
|
322
|
+
else if (ch !== undefined) {
|
|
323
|
+
source += escapeGlobLiteral(ch);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
source += '$';
|
|
327
|
+
return new RegExp(source);
|
|
328
|
+
}
|
|
329
|
+
/** True when root-relative `filePath` matches any glob in `globs` (spec-007 §3 ignore-path exclusion). */
|
|
330
|
+
function matchesIgnoreGlob(filePath, globs) {
|
|
331
|
+
return globs.some((glob) => globToRegExp(glob).test(filePath));
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Run the full scan procedure (spec-007 §4) over `root`'s tracked/staged content under the scan
|
|
335
|
+
* surface (§1): list the indexed blobs via `git ls-files -s`, read them from the object store, skip
|
|
336
|
+
* binaries (§1), apply the `security-ignore` list (§3) as a per-file `pathIgnored` flag, and scan every
|
|
337
|
+
* remaining file's text with {@link scanText}.
|
|
338
|
+
*
|
|
339
|
+
* **Contract — the git index, not the working tree (`bug-015`).** Both the file list and the content
|
|
340
|
+
* come from the index: the scan judges exactly what the next commit would contain, which is what the
|
|
341
|
+
* pre-commit / pre-publish gate of spec-007 §4 step 5 must judge. So a file deleted on disk but still
|
|
342
|
+
* indexed is scanned from its staged blob; a staged deletion is not listed; an unstaged edit — clean or
|
|
343
|
+
* leaky — is not seen until it is staged. (Before `bug-015` the list came from the index but the bytes
|
|
344
|
+
* from disk, so an unstaged deletion threw `ENOENT` and took the whole scan down.) The
|
|
345
|
+
* `security-ignore` file itself is read from disk: it configures the scan, it is not scanned content.
|
|
346
|
+
* `root` must be a git work tree.
|
|
347
|
+
*
|
|
348
|
+
* This is the concrete entry point the REQ-SEC-08 Fit Criterion ("a scan of committed `.wingfoil/`
|
|
349
|
+
* content matches 0 known secret patterns") is checkable against: `scanProjectSurface(root).blocking`
|
|
350
|
+
* must be empty for a hygienic project. Pair that with {@link ScanResult.filesScanned} — an empty
|
|
351
|
+
* `blocking` list only means "clean" once it is known that files were actually read; `filesScanned`
|
|
352
|
+
* is `0` when no surface root exists or the index is empty under it.
|
|
353
|
+
*/
|
|
354
|
+
function scanProjectSurface(root, options = {}) {
|
|
355
|
+
const surfaceRoots = options.surfaceRoots ?? exports.SCAN_SURFACE_ROOTS;
|
|
356
|
+
const ignoreFilePath = options.ignoreFilePath ?? exports.DEFAULT_IGNORE_FILE;
|
|
357
|
+
const ignoreGlobs = loadIgnoreGlobs((0, path_1.join)(root, ignoreFilePath));
|
|
358
|
+
const results = [];
|
|
359
|
+
for (const { path, content } of readIndexedBlobs(root, listIndexedBlobs(root, surfaceRoots))) {
|
|
360
|
+
if (isBinaryContent(content))
|
|
361
|
+
continue;
|
|
362
|
+
const pathIgnored = matchesIgnoreGlob(path, ignoreGlobs);
|
|
363
|
+
results.push(scanText(content.toString('utf-8'), path, { pathIgnored }));
|
|
364
|
+
}
|
|
365
|
+
return mergeScanResults(results);
|
|
366
|
+
}
|
|
367
|
+
//# sourceMappingURL=secret-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-scan.js","sourceRoot":"","sources":["../../src/validation/secret-scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two-pass validation pipeline (spec-009-validation-strategy §1).
|
|
3
|
+
*
|
|
4
|
+
* Pass 1 is the structural Zod parse (`schema.safeParse`); on failure it is fatal and Pass 2 never
|
|
5
|
+
* runs. On success the unknown-field warning fires once (spec-009 §2), then every caller-supplied
|
|
6
|
+
* semantic check (Pass 2) executes in order. The typed, parsed data is returned.
|
|
7
|
+
*/
|
|
8
|
+
import type { ZodType } from 'zod';
|
|
9
|
+
/** A Pass-2 semantic / cross-field check: returns silently on success, throws on failure. */
|
|
10
|
+
export type SemanticCheck = () => void;
|
|
11
|
+
/** Optional inputs to {@link runValidation}: the Pass-1 error-code map and the ordered Pass-2 checks. */
|
|
12
|
+
export interface RunValidationOptions {
|
|
13
|
+
/** Dot-path → `E_*` code map for Pass-1 issues (spec-009 §3). */
|
|
14
|
+
errorMap?: Record<string, string>;
|
|
15
|
+
/** Pass-2 checks, run in order only after Pass 1 succeeds. */
|
|
16
|
+
semanticChecks?: SemanticCheck[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Run the two-pass pipeline (spec-009 §1) for one file: Pass 1 is `schema.safeParse` (fatal on
|
|
20
|
+
* failure, mapped via {@link toValidationError}); on success the unknown-field warning fires once,
|
|
21
|
+
* then every `semanticChecks` entry runs in order. Returns the typed, parsed data.
|
|
22
|
+
*/
|
|
23
|
+
export declare function runValidation<T>(schema: ZodType<T>, raw: unknown, filePath: string, opts?: RunValidationOptions): T;
|
|
24
|
+
//# sourceMappingURL=two-pass.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"two-pass.d.ts","sourceRoot":"","sources":["../../src/validation/two-pass.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAKnC,6FAA6F;AAC7F,MAAM,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC;AAEvC,yGAAyG;AACzG,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,8DAA8D;IAC9D,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAClB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,oBAAyB,GAC9B,CAAC,CAeH"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runValidation = runValidation;
|
|
4
|
+
const error_mapper_1 = require("./error-mapper");
|
|
5
|
+
const warning_1 = require("./warning");
|
|
6
|
+
/**
|
|
7
|
+
* Run the two-pass pipeline (spec-009 §1) for one file: Pass 1 is `schema.safeParse` (fatal on
|
|
8
|
+
* failure, mapped via {@link toValidationError}); on success the unknown-field warning fires once,
|
|
9
|
+
* then every `semanticChecks` entry runs in order. Returns the typed, parsed data.
|
|
10
|
+
*/
|
|
11
|
+
function runValidation(schema, raw, filePath, opts = {}) {
|
|
12
|
+
const result = schema.safeParse(raw);
|
|
13
|
+
if (!result.success) {
|
|
14
|
+
// Pass 1 fatal — Pass 2 never runs.
|
|
15
|
+
throw (0, error_mapper_1.toValidationError)(result.error, filePath, opts.errorMap);
|
|
16
|
+
}
|
|
17
|
+
// Every consuming schema is a `.passthrough()` object (spec-009 Consequences), so it exposes
|
|
18
|
+
// `.shape`; the cast narrows the generic `ZodType<T>` to that structural view for the warning.
|
|
19
|
+
(0, warning_1.emitUnknownFieldWarning)(raw, schema, filePath); // fires regardless of Pass-2 outcome (spec-009 §2)
|
|
20
|
+
for (const check of opts.semanticChecks ?? [])
|
|
21
|
+
check(); // Pass 2
|
|
22
|
+
return result.data;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=two-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"two-pass.js","sourceRoot":"","sources":["../../src/validation/two-pass.ts"],"names":[],"mappings":";;AA4BA,sCAoBC;AAvCD,iDAAmD;AACnD,uCAA8D;AAa9D;;;;GAIG;AACH,SAAgB,aAAa,CAC3B,MAAkB,EAClB,GAAY,EACZ,QAAgB,EAChB,OAA6B,EAAE;IAE/B,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,oCAAoC;QACpC,MAAM,IAAA,gCAAiB,EAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjE,CAAC;IACD,6FAA6F;IAC7F,+FAA+F;IAC/F,IAAA,iCAAuB,EACrB,GAA8B,EAC9B,MAA6B,EAC7B,QAAQ,CACT,CAAC,CAAC,mDAAmD;IACtD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,cAAc,IAAI,EAAE;QAAE,KAAK,EAAE,CAAC,CAAC,SAAS;IACjE,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unknown-field warning policy for `.passthrough()` schemas (spec-009-validation-strategy §2).
|
|
3
|
+
*
|
|
4
|
+
* Unknown-ness is decided against the schema's own declared key set (`schema.shape`), NOT against
|
|
5
|
+
* the passthrough-parsed output. Under `.passthrough()` the parsed object retains every unknown
|
|
6
|
+
* key, so a `raw` vs `parsed` diff is always empty — the "known-defective mechanism" spec-009 §2
|
|
7
|
+
* explicitly warns against. Diffing raw keys against `schema.shape` is the correct mechanism.
|
|
8
|
+
*
|
|
9
|
+
* The same diff is applied recursively at every nesting level that is itself a `.passthrough()`
|
|
10
|
+
* object schema (spec-009 §2: "the same diff is applied recursively at each nesting level that
|
|
11
|
+
* itself has a `.passthrough()` schema, so unknown fields inside nested config blocks are reported
|
|
12
|
+
* too, not only at the document root"). This matters because the files this module guards —
|
|
13
|
+
* `memory.yaml`, `dna.yaml`, `workflows.yaml` — all carry deeply nested config blocks (per-type
|
|
14
|
+
* state machines, phase objects, etc.), where an unknown field would otherwise go undetected.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Minimal structural view of a Zod object schema: only its declared top-level `shape` is needed.
|
|
18
|
+
* Typed structurally rather than as `AnyZodObject` (spec-009 §2's listing) because Zod 4 — the
|
|
19
|
+
* version pinned in `dna.yaml` / `package.json` (`zod@^4`) — no longer exports the `AnyZodObject`
|
|
20
|
+
* alias; every `.passthrough()` object schema still exposes `.shape`, which is all this check reads.
|
|
21
|
+
*/
|
|
22
|
+
export interface HasShape {
|
|
23
|
+
readonly shape: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Emit one stderr warning (spec-009 §2) listing every raw key not declared in `schema`'s shape,
|
|
27
|
+
* recursively through nested passthrough object/array-of-object schemas. Writes nothing when there
|
|
28
|
+
* are no unknown fields.
|
|
29
|
+
*/
|
|
30
|
+
export declare function emitUnknownFieldWarning(raw: Record<string, unknown>, schema: HasShape, filePath: string): void;
|
|
31
|
+
//# sourceMappingURL=warning.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warning.d.ts","sourceRoot":"","sources":["../../src/validation/warning.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAwED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,MAAM,EAAE,QAAQ,EAChB,QAAQ,EAAE,MAAM,GACf,IAAI,CAKN"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unknown-field warning policy for `.passthrough()` schemas (spec-009-validation-strategy §2).
|
|
4
|
+
*
|
|
5
|
+
* Unknown-ness is decided against the schema's own declared key set (`schema.shape`), NOT against
|
|
6
|
+
* the passthrough-parsed output. Under `.passthrough()` the parsed object retains every unknown
|
|
7
|
+
* key, so a `raw` vs `parsed` diff is always empty — the "known-defective mechanism" spec-009 §2
|
|
8
|
+
* explicitly warns against. Diffing raw keys against `schema.shape` is the correct mechanism.
|
|
9
|
+
*
|
|
10
|
+
* The same diff is applied recursively at every nesting level that is itself a `.passthrough()`
|
|
11
|
+
* object schema (spec-009 §2: "the same diff is applied recursively at each nesting level that
|
|
12
|
+
* itself has a `.passthrough()` schema, so unknown fields inside nested config blocks are reported
|
|
13
|
+
* too, not only at the document root"). This matters because the files this module guards —
|
|
14
|
+
* `memory.yaml`, `dna.yaml`, `workflows.yaml` — all carry deeply nested config blocks (per-type
|
|
15
|
+
* state machines, phase objects, etc.), where an unknown field would otherwise go undetected.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.emitUnknownFieldWarning = emitUnknownFieldWarning;
|
|
19
|
+
/** True when `v` is a Zod object schema (structurally: exposes a `.shape` record). */
|
|
20
|
+
function isHasShape(v) {
|
|
21
|
+
return (typeof v === 'object' &&
|
|
22
|
+
v !== null &&
|
|
23
|
+
'shape' in v &&
|
|
24
|
+
typeof v.shape === 'object' &&
|
|
25
|
+
v.shape !== null);
|
|
26
|
+
}
|
|
27
|
+
/** True when `v` is a Zod array schema (structurally: exposes an `.element` schema). */
|
|
28
|
+
function isHasElement(v) {
|
|
29
|
+
return typeof v === 'object' && v !== null && 'element' in v;
|
|
30
|
+
}
|
|
31
|
+
/** True for a plain (non-array) object — a candidate raw value to recurse into. */
|
|
32
|
+
function isPlainObject(v) {
|
|
33
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Recursively collect the paths of every raw key that is not declared in the schema's shape.
|
|
37
|
+
* Top-level unknowns are reported by bare name (`mysteryField`); nested unknowns carry a dotted /
|
|
38
|
+
* indexed path (`nested.field`, `phases[0].field`) so the operator can locate the offending block.
|
|
39
|
+
*/
|
|
40
|
+
function collectUnknownFields(raw, schema, prefix) {
|
|
41
|
+
const known = new Set(Object.keys(schema.shape));
|
|
42
|
+
const out = [];
|
|
43
|
+
for (const key of Object.keys(raw)) {
|
|
44
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
45
|
+
if (!known.has(key)) {
|
|
46
|
+
out.push(path);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Known key: descend when the declared field is itself a passthrough object schema (or an
|
|
50
|
+
// array of them) AND the raw value has the matching shape. Anything else (scalars, records,
|
|
51
|
+
// mismatched raw shapes) is left to Pass-1's own field-level validation.
|
|
52
|
+
const fieldSchema = schema.shape[key];
|
|
53
|
+
const rawValue = raw[key];
|
|
54
|
+
if (isHasShape(fieldSchema) && isPlainObject(rawValue)) {
|
|
55
|
+
out.push(...collectUnknownFields(rawValue, fieldSchema, path));
|
|
56
|
+
}
|
|
57
|
+
else if (isHasElement(fieldSchema) && Array.isArray(rawValue)) {
|
|
58
|
+
const elementSchema = fieldSchema.element;
|
|
59
|
+
if (isHasShape(elementSchema)) {
|
|
60
|
+
rawValue.forEach((item, index) => {
|
|
61
|
+
if (isPlainObject(item)) {
|
|
62
|
+
out.push(...collectUnknownFields(item, elementSchema, `${path}[${index}]`));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Emit one stderr warning (spec-009 §2) listing every raw key not declared in `schema`'s shape,
|
|
72
|
+
* recursively through nested passthrough object/array-of-object schemas. Writes nothing when there
|
|
73
|
+
* are no unknown fields.
|
|
74
|
+
*/
|
|
75
|
+
function emitUnknownFieldWarning(raw, schema, filePath) {
|
|
76
|
+
const unknown = collectUnknownFields(raw, schema, '');
|
|
77
|
+
if (unknown.length > 0) {
|
|
78
|
+
process.stderr.write(`Warning: ${filePath}: unknown field(s) ignored: ${unknown.join(', ')}\n`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=warning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warning.js","sourceRoot":"","sources":["../../src/validation/warning.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse `text` as YAML, returning the resulting data structure (`undefined` for an empty document —
|
|
3
|
+
* that is a valid, if useless, YAML document, not a parse failure).
|
|
4
|
+
*
|
|
5
|
+
* @throws {@link ValidationError} `E_YAML_PARSE_ERROR` (exit 2) if `text` cannot be parsed as YAML
|
|
6
|
+
* at all. Zod never runs in that case (spec-009 §1).
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseYaml(text: string, filePath: string): unknown;
|
|
9
|
+
//# sourceMappingURL=yaml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yaml.d.ts","sourceRoot":"","sources":["../../src/validation/yaml.ts"],"names":[],"mappings":"AAYA;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOjE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseYaml = parseYaml;
|
|
4
|
+
/**
|
|
5
|
+
* Shared pre-Zod YAML parse step (spec-009-validation-strategy §1, Pass 1 step 1: "Parse the raw
|
|
6
|
+
* text as YAML. A parse failure never reaches Zod — see E_YAML_PARSE_ERROR below."). Every pillar
|
|
7
|
+
* loader (memory.yaml, dna.yaml, workflows.yaml + its included workflow files, directive
|
|
8
|
+
* frontmatter) calls this before handing the result to `runValidation`, so a malformed file always
|
|
9
|
+
* surfaces as the same `E_YAML_PARSE_ERROR` / exit-2 `ValidationError` instead of each loader
|
|
10
|
+
* re-wrapping `js-yaml` itself.
|
|
11
|
+
*/
|
|
12
|
+
const js_yaml_1 = require("js-yaml");
|
|
13
|
+
const errors_1 = require("./errors");
|
|
14
|
+
/**
|
|
15
|
+
* Parse `text` as YAML, returning the resulting data structure (`undefined` for an empty document —
|
|
16
|
+
* that is a valid, if useless, YAML document, not a parse failure).
|
|
17
|
+
*
|
|
18
|
+
* @throws {@link ValidationError} `E_YAML_PARSE_ERROR` (exit 2) if `text` cannot be parsed as YAML
|
|
19
|
+
* at all. Zod never runs in that case (spec-009 §1).
|
|
20
|
+
*/
|
|
21
|
+
function parseYaml(text, filePath) {
|
|
22
|
+
try {
|
|
23
|
+
return (0, js_yaml_1.load)(text);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
27
|
+
throw errors_1.ValidationError.yamlParse(filePath, message);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=yaml.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yaml.js","sourceRoot":"","sources":["../../src/validation/yaml.ts"],"names":[],"mappings":";;AAmBA,8BAOC;AA1BD;;;;;;;GAOG;AACH,qCAA+B;AAE/B,qCAA2C;AAE3C;;;;;;GAMG;AACH,SAAgB,SAAS,CAAC,IAAY,EAAE,QAAgB;IACtD,IAAI,CAAC;QACH,OAAO,IAAA,cAAI,EAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,wBAAe,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACrD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `workflow` module — Project Workflow pillar; phases/steps/atomic actions, `include()` composition
|
|
3
|
+
* (P4.1). `workflows.yaml`'s Layer-1 manifest + Layer-2 Workflow DSL schemas
|
|
4
|
+
* (spec-003-workflows-yaml-schema) live here — the pillar owns its own independent schema
|
|
5
|
+
* (REQ-SYS-02); `src/core`'s loader wires it through the shared validation pipeline. Further
|
|
6
|
+
* Workflow behavior (engine execution, `wingfoil workflow *`) lands in later tasks.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MODULE_NAME: "workflow";
|
|
9
|
+
export { WorkflowsYaml, Workflow, Phase } from './schema';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/workflow/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAG,UAAmB,CAAC;AAE/C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC"}
|