@intentius/chant 0.6.0 → 0.8.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/dist/attrref.d.ts +35 -0
- package/dist/attrref.d.ts.map +1 -0
- package/dist/audit/catalog.d.ts +65 -0
- package/dist/audit/catalog.d.ts.map +1 -0
- package/dist/audit/core.d.ts +76 -0
- package/dist/audit/core.d.ts.map +1 -0
- package/dist/audit/discover.d.ts +76 -0
- package/dist/audit/discover.d.ts.map +1 -0
- package/dist/audit/fetch.d.ts +90 -0
- package/dist/audit/fetch.d.ts.map +1 -0
- package/dist/audit/proof.d.ts +53 -0
- package/dist/audit/proof.d.ts.map +1 -0
- package/dist/audit/report-html.d.ts +32 -0
- package/dist/audit/report-html.d.ts.map +1 -0
- package/dist/audit/report-model.d.ts +119 -0
- package/dist/audit/report-model.d.ts.map +1 -0
- package/dist/audit/report.d.ts +31 -0
- package/dist/audit/report.d.ts.map +1 -0
- package/dist/audit/rules-doc.d.ts +11 -0
- package/dist/audit/rules-doc.d.ts.map +1 -0
- package/dist/build.d.ts +120 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/builder.d.ts +56 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/child-project.d.ts +34 -0
- package/dist/child-project.d.ts.map +1 -0
- package/dist/cli/commands/audit.d.ts +60 -0
- package/dist/cli/commands/audit.d.ts.map +1 -0
- package/dist/cli/commands/build.d.ts +70 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/check-lexicon.d.ts +19 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -0
- package/dist/cli/commands/describe.d.ts +39 -0
- package/dist/cli/commands/describe.d.ts.map +1 -0
- package/dist/cli/commands/diff.d.ts +32 -0
- package/dist/cli/commands/diff.d.ts.map +1 -0
- package/dist/cli/commands/doctor.d.ts +11 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/import.d.ts +68 -0
- package/dist/cli/commands/import.d.ts.map +1 -0
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +9 -0
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -0
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts +9 -0
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -0
- package/dist/cli/commands/init-lexicon/templates/examples.d.ts +8 -0
- package/dist/cli/commands/init-lexicon/templates/examples.d.ts.map +1 -0
- package/dist/cli/commands/init-lexicon/templates/lint.d.ts +8 -0
- package/dist/cli/commands/init-lexicon/templates/lint.d.ts.map +1 -0
- package/dist/cli/commands/init-lexicon/templates/lsp.d.ts +6 -0
- package/dist/cli/commands/init-lexicon/templates/lsp.d.ts.map +1 -0
- package/dist/cli/commands/init-lexicon/templates/plugin.d.ts +12 -0
- package/dist/cli/commands/init-lexicon/templates/plugin.d.ts.map +1 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts +19 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -0
- package/dist/cli/commands/init-lexicon/templates/spec.d.ts +6 -0
- package/dist/cli/commands/init-lexicon/templates/spec.d.ts.map +1 -0
- package/dist/cli/commands/init-lexicon/templates/tests.d.ts +12 -0
- package/dist/cli/commands/init-lexicon/templates/tests.d.ts.map +1 -0
- package/dist/cli/commands/init-lexicon.d.ts +37 -0
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -0
- package/dist/cli/commands/init.d.ts +48 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/licenses.d.ts +23 -0
- package/dist/cli/commands/licenses.d.ts.map +1 -0
- package/dist/cli/commands/lint.d.ts +52 -0
- package/dist/cli/commands/lint.d.ts.map +1 -0
- package/dist/cli/commands/list.d.ts +38 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/migrate.d.ts +51 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -0
- package/dist/cli/commands/onboard.d.ts +19 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -0
- package/dist/cli/commands/update.d.ts +29 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/vendor.d.ts +84 -0
- package/dist/cli/commands/vendor.d.ts.map +1 -0
- package/dist/cli/conflict-check.d.ts +18 -0
- package/dist/cli/conflict-check.d.ts.map +1 -0
- package/dist/cli/debug.d.ts +5 -0
- package/dist/cli/debug.d.ts.map +1 -0
- package/dist/cli/format.d.ts +36 -0
- package/dist/cli/format.d.ts.map +1 -0
- package/dist/cli/handlers/build.d.ts +3 -0
- package/dist/cli/handlers/build.d.ts.map +1 -0
- package/dist/cli/handlers/dev.d.ts +7 -0
- package/dist/cli/handlers/dev.d.ts.map +1 -0
- package/dist/cli/handlers/graph.d.ts +8 -0
- package/dist/cli/handlers/graph.d.ts.map +1 -0
- package/dist/cli/handlers/init.d.ts +4 -0
- package/dist/cli/handlers/init.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle.d.ts +39 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -0
- package/dist/cli/handlers/lint.d.ts +3 -0
- package/dist/cli/handlers/lint.d.ts.map +1 -0
- package/dist/cli/handlers/migrate.d.ts +3 -0
- package/dist/cli/handlers/migrate.d.ts.map +1 -0
- package/dist/cli/handlers/misc.d.ts +8 -0
- package/dist/cli/handlers/misc.d.ts.map +1 -0
- package/dist/cli/handlers/run-client.d.ts +107 -0
- package/dist/cli/handlers/run-client.d.ts.map +1 -0
- package/dist/cli/handlers/run-report.d.ts +5 -0
- package/dist/cli/handlers/run-report.d.ts.map +1 -0
- package/dist/cli/handlers/run.d.ts +27 -0
- package/dist/cli/handlers/run.d.ts.map +1 -0
- package/dist/cli/handlers/serve.d.ts +5 -0
- package/dist/cli/handlers/serve.d.ts.map +1 -0
- package/dist/cli/handlers/vendor.d.ts +9 -0
- package/dist/cli/handlers/vendor.d.ts.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/lsp/capabilities.d.ts +21 -0
- package/dist/cli/lsp/capabilities.d.ts.map +1 -0
- package/dist/cli/lsp/diagnostics.d.ts +30 -0
- package/dist/cli/lsp/diagnostics.d.ts.map +1 -0
- package/dist/cli/lsp/server.d.ts +106 -0
- package/dist/cli/lsp/server.d.ts.map +1 -0
- package/dist/cli/main.d.ts +7 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/mcp/lifecycle-tools.d.ts +15 -0
- package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -0
- package/dist/cli/mcp/op-tools.d.ts +7 -0
- package/dist/cli/mcp/op-tools.d.ts.map +1 -0
- package/dist/cli/mcp/resource-handlers.d.ts +28 -0
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -0
- package/dist/cli/mcp/resources/context.d.ts +6 -0
- package/dist/cli/mcp/resources/context.d.ts.map +1 -0
- package/dist/cli/mcp/server.d.ts +44 -0
- package/dist/cli/mcp/server.d.ts.map +1 -0
- package/dist/cli/mcp/tools/build.d.ts +31 -0
- package/dist/cli/mcp/tools/build.d.ts.map +1 -0
- package/dist/cli/mcp/tools/explain.d.ts +27 -0
- package/dist/cli/mcp/tools/explain.d.ts.map +1 -0
- package/dist/cli/mcp/tools/import.d.ts +26 -0
- package/dist/cli/mcp/tools/import.d.ts.map +1 -0
- package/dist/cli/mcp/tools/lint.d.ts +26 -0
- package/dist/cli/mcp/tools/lint.d.ts.map +1 -0
- package/dist/cli/mcp/tools/scaffold.d.ts +31 -0
- package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -0
- package/dist/cli/mcp/tools/search.d.ts +31 -0
- package/dist/cli/mcp/tools/search.d.ts.map +1 -0
- package/dist/cli/mcp/types.d.ts +42 -0
- package/dist/cli/mcp/types.d.ts.map +1 -0
- package/dist/cli/plugins.d.ts +21 -0
- package/dist/cli/plugins.d.ts.map +1 -0
- package/dist/cli/registry.d.ts +100 -0
- package/dist/cli/registry.d.ts.map +1 -0
- package/dist/cli/reporters/stylish.d.ts +26 -0
- package/dist/cli/reporters/stylish.d.ts.map +1 -0
- package/dist/cli/watch.d.ts +29 -0
- package/dist/cli/watch.d.ts.map +1 -0
- package/dist/codegen/bound-property-types.d.ts +38 -0
- package/dist/codegen/bound-property-types.d.ts.map +1 -0
- package/dist/codegen/coverage.d.ts +57 -0
- package/dist/codegen/coverage.d.ts.map +1 -0
- package/dist/codegen/docs-file-markers.d.ts +16 -0
- package/dist/codegen/docs-file-markers.d.ts.map +1 -0
- package/dist/codegen/docs-rule-scanning.d.ts +14 -0
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -0
- package/dist/codegen/docs-rules.d.ts +17 -0
- package/dist/codegen/docs-rules.d.ts.map +1 -0
- package/dist/codegen/docs-sections.d.ts +11 -0
- package/dist/codegen/docs-sections.d.ts.map +1 -0
- package/dist/codegen/docs-sidebar.d.ts +6 -0
- package/dist/codegen/docs-sidebar.d.ts.map +1 -0
- package/dist/codegen/docs-types.d.ts +84 -0
- package/dist/codegen/docs-types.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts +28 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/fetch.d.ts +76 -0
- package/dist/codegen/fetch.d.ts.map +1 -0
- package/dist/codegen/generate-post-synth-barrel.d.ts +32 -0
- package/dist/codegen/generate-post-synth-barrel.d.ts.map +1 -0
- package/dist/codegen/generate-registry.d.ts +40 -0
- package/dist/codegen/generate-registry.d.ts.map +1 -0
- package/dist/codegen/generate-runtime-index.d.ts +40 -0
- package/dist/codegen/generate-runtime-index.d.ts.map +1 -0
- package/dist/codegen/generate-typescript.d.ts +48 -0
- package/dist/codegen/generate-typescript.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +92 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/json-patch.d.ts +12 -0
- package/dist/codegen/json-patch.d.ts.map +1 -0
- package/dist/codegen/json-schema.d.ts +82 -0
- package/dist/codegen/json-schema.d.ts.map +1 -0
- package/dist/codegen/naming.d.ts +64 -0
- package/dist/codegen/naming.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +64 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/codegen/topo-sort.d.ts +13 -0
- package/dist/codegen/topo-sort.d.ts.map +1 -0
- package/dist/codegen/typecheck.d.ts +10 -0
- package/dist/codegen/typecheck.d.ts.map +1 -0
- package/dist/codegen/validate.d.ts +45 -0
- package/dist/codegen/validate.d.ts.map +1 -0
- package/dist/composite.d.ts +116 -0
- package/dist/composite.d.ts.map +1 -0
- package/dist/config.d.ts +78 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/declarable.d.ts +34 -0
- package/dist/declarable.d.ts.map +1 -0
- package/dist/detectLexicon.d.ts +10 -0
- package/dist/detectLexicon.d.ts.map +1 -0
- package/dist/discovery/cache.d.ts +36 -0
- package/dist/discovery/cache.d.ts.map +1 -0
- package/dist/discovery/collect.d.ts +17 -0
- package/dist/discovery/collect.d.ts.map +1 -0
- package/dist/discovery/cycles.d.ts +23 -0
- package/dist/discovery/cycles.d.ts.map +1 -0
- package/dist/discovery/files.d.ts +7 -0
- package/dist/discovery/files.d.ts.map +1 -0
- package/dist/discovery/graph.d.ts +8 -0
- package/dist/discovery/graph.d.ts.map +1 -0
- package/dist/discovery/import.d.ts +9 -0
- package/dist/discovery/import.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +25 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/resolve.d.ts +10 -0
- package/dist/discovery/resolve.d.ts.map +1 -0
- package/dist/errors.d.ts +49 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/import/base-parser.d.ts +19 -0
- package/dist/import/base-parser.d.ts.map +1 -0
- package/dist/import/generator.d.ts +20 -0
- package/dist/import/generator.d.ts.map +1 -0
- package/dist/import/ir-utils.d.ts +25 -0
- package/dist/import/ir-utils.d.ts.map +1 -0
- package/dist/import/parser.d.ts +39 -0
- package/dist/import/parser.d.ts.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/intrinsic-interpolation.d.ts +24 -0
- package/dist/intrinsic-interpolation.d.ts.map +1 -0
- package/dist/intrinsic.d.ts +24 -0
- package/dist/intrinsic.d.ts.map +1 -0
- package/dist/lexicon-integrity.d.ts +25 -0
- package/dist/lexicon-integrity.d.ts.map +1 -0
- package/dist/lexicon-manifest.d.ts +9 -0
- package/dist/lexicon-manifest.d.ts.map +1 -0
- package/dist/lexicon-output.d.ts +66 -0
- package/dist/lexicon-output.d.ts.map +1 -0
- package/dist/lexicon-plugin-helpers.d.ts +59 -0
- package/dist/lexicon-plugin-helpers.d.ts.map +1 -0
- package/dist/lexicon-schema.d.ts +82 -0
- package/dist/lexicon-schema.d.ts.map +1 -0
- package/dist/lexicon.d.ts +383 -0
- package/dist/lexicon.d.ts.map +1 -0
- package/dist/lifecycle/affected.d.ts +57 -0
- package/dist/lifecycle/affected.d.ts.map +1 -0
- package/dist/lifecycle/change-set.d.ts +90 -0
- package/dist/lifecycle/change-set.d.ts.map +1 -0
- package/dist/lifecycle/digest.d.ts +21 -0
- package/dist/lifecycle/digest.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +72 -0
- package/dist/lifecycle/git.d.ts.map +1 -0
- package/dist/lifecycle/index.d.ts +8 -0
- package/dist/lifecycle/index.d.ts.map +1 -0
- package/dist/lifecycle/live-diff.d.ts +67 -0
- package/dist/lifecycle/live-diff.d.ts.map +1 -0
- package/dist/lifecycle/snapshot.d.ts +20 -0
- package/dist/lifecycle/snapshot.d.ts.map +1 -0
- package/dist/lifecycle/types.d.ts +61 -0
- package/dist/lifecycle/types.d.ts.map +1 -0
- package/dist/lint/config.d.ts +100 -0
- package/dist/lint/config.d.ts.map +1 -0
- package/dist/lint/declarative.d.ts +56 -0
- package/dist/lint/declarative.d.ts.map +1 -0
- package/dist/lint/discover.d.ts +35 -0
- package/dist/lint/discover.d.ts.map +1 -0
- package/dist/lint/engine.d.ts +21 -0
- package/dist/lint/engine.d.ts.map +1 -0
- package/dist/lint/named-checks.d.ts +19 -0
- package/dist/lint/named-checks.d.ts.map +1 -0
- package/dist/lint/parser.d.ts +9 -0
- package/dist/lint/parser.d.ts.map +1 -0
- package/dist/lint/policy.d.ts +22 -0
- package/dist/lint/policy.d.ts.map +1 -0
- package/dist/lint/post-synth.d.ts +69 -0
- package/dist/lint/post-synth.d.ts.map +1 -0
- package/dist/lint/presets/strict.json +19 -0
- package/dist/lint/rule-loader.d.ts +16 -0
- package/dist/lint/rule-loader.d.ts.map +1 -0
- package/dist/lint/rule-registry.d.ts +37 -0
- package/dist/lint/rule-registry.d.ts.map +1 -0
- package/dist/lint/rule.d.ts +82 -0
- package/dist/lint/rule.d.ts.map +1 -0
- package/dist/lint/rules/composite-scope.d.ts +18 -0
- package/dist/lint/rules/composite-scope.d.ts.map +1 -0
- package/dist/lint/rules/cor017-composite-name-match.d.ts +3 -0
- package/dist/lint/rules/cor017-composite-name-match.d.ts.map +1 -0
- package/dist/lint/rules/cor018-composite-prefer-lexicon-type.d.ts +3 -0
- package/dist/lint/rules/cor018-composite-prefer-lexicon-type.d.ts.map +1 -0
- package/dist/lint/rules/declarable-naming-convention.d.ts +3 -0
- package/dist/lint/rules/declarable-naming-convention.d.ts.map +1 -0
- package/dist/lint/rules/evl001-non-literal-expression.d.ts +3 -0
- package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -0
- package/dist/lint/rules/evl002-control-flow-resource.d.ts +3 -0
- package/dist/lint/rules/evl002-control-flow-resource.d.ts.map +1 -0
- package/dist/lint/rules/evl003-dynamic-property-access.d.ts +3 -0
- package/dist/lint/rules/evl003-dynamic-property-access.d.ts.map +1 -0
- package/dist/lint/rules/evl004-spread-non-const.d.ts +3 -0
- package/dist/lint/rules/evl004-spread-non-const.d.ts.map +1 -0
- package/dist/lint/rules/evl005-resource-block-body.d.ts +3 -0
- package/dist/lint/rules/evl005-resource-block-body.d.ts.map +1 -0
- package/dist/lint/rules/evl007-invalid-siblings.d.ts +3 -0
- package/dist/lint/rules/evl007-invalid-siblings.d.ts.map +1 -0
- package/dist/lint/rules/evl009-composite-no-constant.d.ts +3 -0
- package/dist/lint/rules/evl009-composite-no-constant.d.ts.map +1 -0
- package/dist/lint/rules/evl010-composite-no-transform.d.ts +3 -0
- package/dist/lint/rules/evl010-composite-no-transform.d.ts.map +1 -0
- package/dist/lint/rules/export-required.d.ts +3 -0
- package/dist/lint/rules/export-required.d.ts.map +1 -0
- package/dist/lint/rules/file-declarable-limit.d.ts +3 -0
- package/dist/lint/rules/file-declarable-limit.d.ts.map +1 -0
- package/dist/lint/rules/flat-declarations.d.ts +3 -0
- package/dist/lint/rules/flat-declarations.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +31 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lint/rules/no-cyclic-declarable-ref.d.ts +3 -0
- package/dist/lint/rules/no-cyclic-declarable-ref.d.ts.map +1 -0
- package/dist/lint/rules/no-redundant-type-import.d.ts +10 -0
- package/dist/lint/rules/no-redundant-type-import.d.ts.map +1 -0
- package/dist/lint/rules/no-redundant-value-cast.d.ts +3 -0
- package/dist/lint/rules/no-redundant-value-cast.d.ts.map +1 -0
- package/dist/lint/rules/no-string-ref.d.ts +3 -0
- package/dist/lint/rules/no-string-ref.d.ts.map +1 -0
- package/dist/lint/rules/no-unused-declarable-import.d.ts +3 -0
- package/dist/lint/rules/no-unused-declarable-import.d.ts.map +1 -0
- package/dist/lint/rules/no-unused-declarable.d.ts +3 -0
- package/dist/lint/rules/no-unused-declarable.d.ts.map +1 -0
- package/dist/lint/rules/single-concern-file.d.ts +3 -0
- package/dist/lint/rules/single-concern-file.d.ts.map +1 -0
- package/dist/lint/selectors.d.ts +21 -0
- package/dist/lint/selectors.d.ts.map +1 -0
- package/dist/lsp/lexicon-providers.d.ts +61 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -0
- package/dist/lsp/types.d.ts +64 -0
- package/dist/lsp/types.d.ts.map +1 -0
- package/dist/mcp/types.d.ts +21 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/op/activity-registry.d.ts +57 -0
- package/dist/op/activity-registry.d.ts.map +1 -0
- package/dist/op/builders.d.ts +69 -0
- package/dist/op/builders.d.ts.map +1 -0
- package/dist/op/discover.d.ts +16 -0
- package/dist/op/discover.d.ts.map +1 -0
- package/dist/op/index.d.ts +11 -0
- package/dist/op/index.d.ts.map +1 -0
- package/dist/op/local-executor.d.ts +54 -0
- package/dist/op/local-executor.d.ts.map +1 -0
- package/dist/op/local-output.d.ts +16 -0
- package/dist/op/local-output.d.ts.map +1 -0
- package/dist/op/resource.d.ts +7 -0
- package/dist/op/resource.d.ts.map +1 -0
- package/dist/op/types.d.ts +71 -0
- package/dist/op/types.d.ts.map +1 -0
- package/dist/ownership.d.ts +75 -0
- package/dist/ownership.d.ts.map +1 -0
- package/dist/project-validation.d.ts +28 -0
- package/dist/project-validation.d.ts.map +1 -0
- package/dist/provenance.d.ts +26 -0
- package/dist/provenance.d.ts.map +1 -0
- package/dist/pseudo-parameter.d.ts +32 -0
- package/dist/pseudo-parameter.d.ts.map +1 -0
- package/dist/resource-attributes.d.ts +17 -0
- package/dist/resource-attributes.d.ts.map +1 -0
- package/dist/runtime-adapter.d.ts +41 -0
- package/dist/runtime-adapter.d.ts.map +1 -0
- package/dist/runtime.d.ts +23 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/serializer-walker.d.ts +21 -0
- package/dist/serializer-walker.d.ts.map +1 -0
- package/dist/serializer.d.ts +64 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/sort.d.ts +8 -0
- package/dist/sort.d.ts.map +1 -0
- package/dist/stack-output.d.ts +52 -0
- package/dist/stack-output.d.ts.map +1 -0
- package/dist/toml-emit.d.ts +22 -0
- package/dist/toml-emit.d.ts.map +1 -0
- package/dist/toml-parse.d.ts +14 -0
- package/dist/toml-parse.d.ts.map +1 -0
- package/dist/toml-utils.d.ts +20 -0
- package/dist/toml-utils.d.ts.map +1 -0
- package/dist/toml.d.ts +9 -0
- package/dist/toml.d.ts.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +30 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/validation.d.ts +42 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/yaml.d.ts +42 -0
- package/dist/yaml.d.ts.map +1 -0
- package/package.json +31 -6
- package/src/audit/bundle-decoupling.test.ts +56 -0
- package/src/audit/catalog.test.ts +17 -2
- package/src/audit/catalog.ts +357 -4
- package/src/audit/core.test.ts +15 -0
- package/src/audit/core.ts +0 -0
- package/src/audit/detect-bundle.test.ts +35 -0
- package/src/audit/discover.test.ts +72 -0
- package/src/audit/discover.ts +329 -0
- package/src/audit/edge-init-safety.test.ts +43 -0
- package/src/audit/fetch.test.ts +86 -55
- package/src/audit/fetch.ts +118 -87
- package/src/audit/remote-discovery.test.ts +80 -0
- package/src/audit/report-html.ts +14 -6
- package/src/audit/report-model.ts +13 -1
- package/src/audit/report.test.ts +3 -1
- package/src/audit/report.ts +13 -5
- package/src/audit/rules-doc.ts +4 -0
- package/src/cli/commands/__fixtures__/audit-aws/stack.yaml +11 -0
- package/src/cli/commands/__fixtures__/audit-aws/template.json +7 -0
- package/src/cli/commands/__fixtures__/audit-azure/azuredeploy.json +13 -0
- package/src/cli/commands/__fixtures__/audit-gcp/bucket.yaml +6 -0
- package/src/cli/commands/__fixtures__/audit-gcp/firewall.yaml +9 -0
- package/src/cli/commands/__fixtures__/audit-helm/mychart/Chart.yaml +3 -0
- package/src/cli/commands/__fixtures__/audit-helm/mychart/templates/deployment.yaml +12 -0
- package/src/cli/commands/__fixtures__/audit-helm/mychart/values.yaml +1 -0
- package/src/cli/commands/__fixtures__/audit-mixed/.github/workflows/ci.yml +5 -0
- package/src/cli/commands/__fixtures__/audit-mixed/Dockerfile +2 -0
- package/src/cli/commands/__fixtures__/audit-mixed/cfn/template.json +1 -0
- package/src/cli/commands/__fixtures__/audit-mixed/k8s/deploy.yaml +12 -0
- package/src/cli/commands/audit.test.ts +80 -14
- package/src/cli/commands/audit.ts +28 -126
- package/src/codegen/bound-property-types.test.ts +57 -0
- package/src/codegen/bound-property-types.ts +110 -0
- package/src/codegen/generate-post-synth-barrel.test.ts +37 -0
- package/src/codegen/generate-post-synth-barrel.ts +75 -0
- package/src/codegen/post-synth-barrel-guards.test.ts +63 -0
- package/src/codegen/validate.ts +13 -1
- package/src/lint/discover.ts +1 -1
- package/src/meta/peer-deps.test.ts +29 -0
package/src/audit/fetch.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Remote fetch — pull a repo's
|
|
3
|
-
* on a URL, not just a local path. This is the ONLY audit
|
|
4
|
-
* the network; the core stays pure.
|
|
2
|
+
* Remote fetch — pull a repo's candidate files (all lexicons) from a git host so
|
|
3
|
+
* the auditor can run on a URL, not just a local path. This is the ONLY audit
|
|
4
|
+
* module that touches the network; the core stays pure.
|
|
5
5
|
*
|
|
6
6
|
* SSRF posture: only an allowlisted set of hosts is accepted; request URLs are
|
|
7
7
|
* built from the parsed owner/repo (never a user-controlled host); redirects
|
|
8
8
|
* are refused; and file count / size / total bytes / time are all capped.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type {
|
|
11
|
+
import type { AuditLexicon } from "./core";
|
|
12
12
|
|
|
13
13
|
export interface FetchOptions {
|
|
14
14
|
/** Branch/tag/sha; defaults to the repo's default branch. */
|
|
15
15
|
ref?: string;
|
|
16
16
|
/** Server-side token (lifts rate limits). Never surfaced to callers. */
|
|
17
17
|
token?: string;
|
|
18
|
-
/** Max number of
|
|
18
|
+
/** Max number of files to fetch (default 50). */
|
|
19
19
|
maxFiles?: number;
|
|
20
20
|
/** Max bytes for a single file; larger files are skipped (default 256 KiB). */
|
|
21
21
|
maxBytesPerFile?: number;
|
|
@@ -87,10 +87,6 @@ function authHeaders(kind: HostKind, token?: string): Record<string, string> {
|
|
|
87
87
|
return { "PRIVATE-TOKEN": token };
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
function isYaml(name: string): boolean {
|
|
91
|
-
return name.endsWith(".yml") || name.endsWith(".yaml");
|
|
92
|
-
}
|
|
93
|
-
|
|
94
90
|
function timeoutSignal(ms: number): AbortSignal | undefined {
|
|
95
91
|
return typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function"
|
|
96
92
|
? AbortSignal.timeout(ms)
|
|
@@ -107,7 +103,103 @@ interface ContentsEntry {
|
|
|
107
103
|
encoding?: string;
|
|
108
104
|
}
|
|
109
105
|
|
|
110
|
-
|
|
106
|
+
// ── Whole-repo fetch (all lexicons, not just CI) ─────────────────────────────
|
|
107
|
+
// Mirrors the local walk: list the repo tree, keep candidate paths, fetch their
|
|
108
|
+
// contents (capped), and hand the raw {path, content} set to the shared
|
|
109
|
+
// classifier in discover.ts. SSRF posture is unchanged — allowlisted host,
|
|
110
|
+
// URLs built from parsed owner/repo, redirects refused, counts/bytes capped.
|
|
111
|
+
|
|
112
|
+
/** Module-level JSON GET that refuses redirects (reused by the tree-walk). */
|
|
113
|
+
async function getJsonAt(
|
|
114
|
+
url: string,
|
|
115
|
+
headers: Record<string, string>,
|
|
116
|
+
doFetch: typeof fetch,
|
|
117
|
+
timeoutMs: number,
|
|
118
|
+
): Promise<{ status: number; body: unknown }> {
|
|
119
|
+
let res: Response;
|
|
120
|
+
try {
|
|
121
|
+
res = await doFetch(url, { headers, redirect: "manual", signal: timeoutSignal(timeoutMs) });
|
|
122
|
+
} catch (err) {
|
|
123
|
+
throw new FetchError(`Request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
124
|
+
}
|
|
125
|
+
if (res.status >= 300 && res.status < 400) throw new FetchError(`Refusing to follow redirect from ${url}`);
|
|
126
|
+
if (res.status === 404) return { status: 404, body: null };
|
|
127
|
+
if (!res.ok) throw new FetchError(`${url} returned ${res.status}`);
|
|
128
|
+
return { status: res.status, body: await res.json() };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function projectId(owner: string, repo: string): string {
|
|
132
|
+
return encodeURIComponent(`${owner}/${repo}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The repo's default branch, so the tree request has a concrete ref. */
|
|
136
|
+
async function defaultBranch(host: HostConfig, owner: string, repo: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<string> {
|
|
137
|
+
const url =
|
|
138
|
+
host.kind === "gitlab"
|
|
139
|
+
? `${host.api}/projects/${projectId(owner, repo)}`
|
|
140
|
+
: `${host.api}/repos/${owner}/${repo}`;
|
|
141
|
+
const { body } = await getJsonAt(url, headers, doFetch, ms);
|
|
142
|
+
const branch = (body as { default_branch?: string } | null)?.default_branch;
|
|
143
|
+
return branch && typeof branch === "string" ? branch : "HEAD";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface TreeEntry {
|
|
147
|
+
path: string;
|
|
148
|
+
type: string;
|
|
149
|
+
size?: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** List every blob path in the repo (recursive), with size where the host reports it. */
|
|
153
|
+
async function listTree(host: HostConfig, owner: string, repo: string, ref: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<TreeEntry[]> {
|
|
154
|
+
if (host.kind === "gitlab") {
|
|
155
|
+
const out: TreeEntry[] = [];
|
|
156
|
+
const MAX_PAGES = 20; // 20 * 100 = 2000 entries — bounded; candidate filter + caps trim further
|
|
157
|
+
for (let page = 1; page <= MAX_PAGES; page++) {
|
|
158
|
+
const url = `${host.api}/projects/${projectId(owner, repo)}/repository/tree?recursive=true&per_page=100&page=${page}&ref=${encodeURIComponent(ref)}`;
|
|
159
|
+
const { body } = await getJsonAt(url, headers, doFetch, ms);
|
|
160
|
+
if (!Array.isArray(body) || body.length === 0) break;
|
|
161
|
+
for (const e of body as Array<{ path: string; type: string }>) {
|
|
162
|
+
if (e.type === "blob") out.push({ path: e.path, type: "blob" });
|
|
163
|
+
}
|
|
164
|
+
if (body.length < 100) break;
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
// GitHub / Forgejo (Gitea) share the git/trees recursive API.
|
|
169
|
+
const url = `${host.api}/repos/${owner}/${repo}/git/trees/${encodeURIComponent(ref)}?recursive=1`;
|
|
170
|
+
const { body } = await getJsonAt(url, headers, doFetch, ms);
|
|
171
|
+
const tree = (body as { tree?: TreeEntry[] } | null)?.tree;
|
|
172
|
+
if (!Array.isArray(tree)) return [];
|
|
173
|
+
return tree.filter((e) => e.type === "blob").map((e) => ({ path: e.path, type: "blob", size: e.size }));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Fetch one file's content per host (base64 contents API, or GitLab raw). */
|
|
177
|
+
async function fetchFileContent(host: HostConfig, owner: string, repo: string, path: string, ref: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<string | undefined> {
|
|
178
|
+
if (host.kind === "gitlab") {
|
|
179
|
+
const url = `${host.api}/projects/${projectId(owner, repo)}/repository/files/${encodeURIComponent(path)}/raw?ref=${encodeURIComponent(ref)}`;
|
|
180
|
+
let res: Response;
|
|
181
|
+
try {
|
|
182
|
+
res = await doFetch(url, { headers, redirect: "manual", signal: timeoutSignal(ms) });
|
|
183
|
+
} catch {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
if (!res.ok) return undefined;
|
|
187
|
+
return await res.text();
|
|
188
|
+
}
|
|
189
|
+
const url = `${host.api}/repos/${owner}/${repo}/contents/${path}?ref=${encodeURIComponent(ref)}`;
|
|
190
|
+
const { body } = await getJsonAt(url, headers, doFetch, ms);
|
|
191
|
+
const file = body as ContentsEntry | null;
|
|
192
|
+
if (!file?.content) return undefined;
|
|
193
|
+
return Buffer.from(file.content, (file.encoding as BufferEncoding) ?? "base64").toString("utf-8");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Fetch every candidate file in a repo (all lexicons), so a URL audit covers the
|
|
198
|
+
* same ground as a local one. Returns raw {path, content}; classification (which
|
|
199
|
+
* needs the lexicon plugins) is the caller's job via `classifyFiles`.
|
|
200
|
+
*/
|
|
201
|
+
export async function fetchRepoFiles(url: string, opts: FetchOptions = {}): Promise<Array<{ path: string; content: string }>> {
|
|
202
|
+
const { isCandidatePath } = await import("./discover");
|
|
111
203
|
const { host, owner, repo } = parseRepoUrl(url);
|
|
112
204
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
113
205
|
const cfg = {
|
|
@@ -117,58 +209,24 @@ export async function fetchCiFiles(url: string, opts: FetchOptions = {}): Promis
|
|
|
117
209
|
timeoutMs: opts.timeoutMs ?? DEFAULTS.timeoutMs,
|
|
118
210
|
};
|
|
119
211
|
const headers = authHeaders(host.kind, opts.token);
|
|
212
|
+
const ref = opts.ref ?? (await defaultBranch(host, owner, repo, doFetch, headers, cfg.timeoutMs));
|
|
120
213
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
res = await doFetch(apiUrl, { headers, redirect: "error", signal: timeoutSignal(cfg.timeoutMs) });
|
|
125
|
-
} catch (err) {
|
|
126
|
-
throw new FetchError(`Request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
127
|
-
}
|
|
128
|
-
if (res.status >= 300 && res.status < 400) {
|
|
129
|
-
throw new FetchError(`Refusing to follow redirect from ${apiUrl}`);
|
|
130
|
-
}
|
|
131
|
-
if (res.status === 404) return { status: 404, body: null };
|
|
132
|
-
if (!res.ok) throw new FetchError(`${apiUrl} returned ${res.status}`);
|
|
133
|
-
return { status: res.status, body: await res.json() };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (host.kind === "gitlab") {
|
|
137
|
-
return fetchGitlab(host, owner, repo, opts.ref, cfg, doFetch, headers);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// GitHub / Forgejo share the Gitea-style contents API.
|
|
141
|
-
const dirs = host.kind === "forgejo" ? [".forgejo/workflows", ".github/workflows"] : [".github/workflows"];
|
|
142
|
-
const ref = opts.ref ? `?ref=${encodeURIComponent(opts.ref)}` : "";
|
|
143
|
-
|
|
144
|
-
const candidates: ContentsEntry[] = [];
|
|
145
|
-
const seen = new Set<string>();
|
|
146
|
-
for (const dir of dirs) {
|
|
147
|
-
const { body } = await getJson(`${host.api}/repos/${owner}/${repo}/contents/${dir}${ref}`);
|
|
148
|
-
if (!Array.isArray(body)) continue;
|
|
149
|
-
for (const entry of body as ContentsEntry[]) {
|
|
150
|
-
if (entry.type === "file" && isYaml(entry.name) && !seen.has(entry.path)) {
|
|
151
|
-
seen.add(entry.path);
|
|
152
|
-
candidates.push(entry);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
214
|
+
const tree = await listTree(host, owner, repo, ref, doFetch, headers, cfg.timeoutMs);
|
|
215
|
+
const candidates = tree.filter((e) => isCandidatePath(e.path));
|
|
156
216
|
|
|
157
|
-
const
|
|
217
|
+
const files: Array<{ path: string; content: string }> = [];
|
|
158
218
|
let total = 0;
|
|
159
219
|
for (const entry of candidates) {
|
|
160
|
-
if (
|
|
161
|
-
if ((entry.size ?? 0) > cfg.maxBytesPerFile) continue; // skip oversize
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
if (!file?.content) continue;
|
|
165
|
-
const content = Buffer.from(file.content, (file.encoding as BufferEncoding) ?? "base64").toString("utf-8");
|
|
220
|
+
if (files.length >= cfg.maxFiles) break;
|
|
221
|
+
if ((entry.size ?? 0) > cfg.maxBytesPerFile) continue; // skip oversize when the host reports size
|
|
222
|
+
const content = await fetchFileContent(host, owner, repo, entry.path, ref, doFetch, headers, cfg.timeoutMs);
|
|
223
|
+
if (content === undefined) continue;
|
|
166
224
|
if (content.length > cfg.maxBytesPerFile) continue;
|
|
167
225
|
total += content.length;
|
|
168
|
-
if (total > cfg.maxTotalBytes) throw new FetchError("Repository
|
|
169
|
-
|
|
226
|
+
if (total > cfg.maxTotalBytes) throw new FetchError("Repository files exceed the total size cap.");
|
|
227
|
+
files.push({ path: entry.path, content });
|
|
170
228
|
}
|
|
171
|
-
return
|
|
229
|
+
return files;
|
|
172
230
|
}
|
|
173
231
|
|
|
174
232
|
const SHA40 = /^[0-9a-f]{40}$/;
|
|
@@ -195,7 +253,7 @@ export async function resolveActionSha(
|
|
|
195
253
|
Accept: "application/vnd.github+json",
|
|
196
254
|
...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
|
|
197
255
|
},
|
|
198
|
-
redirect: "
|
|
256
|
+
redirect: "manual",
|
|
199
257
|
signal: timeoutSignal(opts.timeoutMs ?? DEFAULTS.timeoutMs),
|
|
200
258
|
});
|
|
201
259
|
if (!res.ok) return undefined;
|
|
@@ -289,16 +347,16 @@ export async function resolveImageDigest(
|
|
|
289
347
|
const manifestUrl = `https://${parsed.registry}/v2/${parsed.repository}/manifests/${encodeURIComponent(parsed.tag)}`;
|
|
290
348
|
|
|
291
349
|
try {
|
|
292
|
-
let res = await doFetch(manifestUrl, { headers: { Accept: accept }, redirect: "
|
|
350
|
+
let res = await doFetch(manifestUrl, { headers: { Accept: accept }, redirect: "manual", signal: timeoutSignal(ms) });
|
|
293
351
|
if (res.status === 401) {
|
|
294
352
|
const tokenUrl = tokenUrlFromChallenge(res.headers.get("www-authenticate") ?? "");
|
|
295
353
|
if (!tokenUrl) return undefined;
|
|
296
|
-
const tokRes = await doFetch(tokenUrl, { redirect: "
|
|
354
|
+
const tokRes = await doFetch(tokenUrl, { redirect: "manual", signal: timeoutSignal(ms) });
|
|
297
355
|
if (!tokRes.ok) return undefined;
|
|
298
356
|
const tok = (await tokRes.json()) as { token?: string; access_token?: string };
|
|
299
357
|
const bearer = tok.token ?? tok.access_token;
|
|
300
358
|
if (!bearer) return undefined;
|
|
301
|
-
res = await doFetch(manifestUrl, { headers: { Accept: accept, Authorization: `Bearer ${bearer}` }, redirect: "
|
|
359
|
+
res = await doFetch(manifestUrl, { headers: { Accept: accept, Authorization: `Bearer ${bearer}` }, redirect: "manual", signal: timeoutSignal(ms) });
|
|
302
360
|
}
|
|
303
361
|
if (!res.ok) return undefined;
|
|
304
362
|
const digest = res.headers.get("docker-content-digest");
|
|
@@ -331,7 +389,7 @@ export async function resolveRepoCommit(
|
|
|
331
389
|
? `${host.api}/projects/${encodeURIComponent(`${owner}/${repo}`)}/repository/commits?per_page=1`
|
|
332
390
|
: `${host.api}/repos/${owner}/${repo}/commits?per_page=1&limit=1`;
|
|
333
391
|
try {
|
|
334
|
-
const res = await doFetch(apiUrl, { headers, redirect: "
|
|
392
|
+
const res = await doFetch(apiUrl, { headers, redirect: "manual", signal: timeoutSignal(opts.timeoutMs ?? DEFAULTS.timeoutMs) });
|
|
335
393
|
if (!res.ok) return undefined;
|
|
336
394
|
const body = (await res.json()) as Array<{ sha?: string; id?: string }>;
|
|
337
395
|
if (!Array.isArray(body) || body.length === 0) return undefined;
|
|
@@ -342,30 +400,3 @@ export async function resolveRepoCommit(
|
|
|
342
400
|
}
|
|
343
401
|
}
|
|
344
402
|
|
|
345
|
-
async function fetchGitlab(
|
|
346
|
-
host: HostConfig,
|
|
347
|
-
owner: string,
|
|
348
|
-
repo: string,
|
|
349
|
-
ref: string | undefined,
|
|
350
|
-
cfg: { maxBytesPerFile: number; maxTotalBytes: number; timeoutMs: number },
|
|
351
|
-
doFetch: typeof fetch,
|
|
352
|
-
headers: Record<string, string>,
|
|
353
|
-
): Promise<AuditInput[]> {
|
|
354
|
-
const projectId = encodeURIComponent(`${owner}/${repo}`);
|
|
355
|
-
const refQ = `?ref=${encodeURIComponent(ref ?? "HEAD")}`;
|
|
356
|
-
const apiUrl = `${host.api}/projects/${projectId}/repository/files/${encodeURIComponent(".gitlab-ci.yml")}/raw${refQ}`;
|
|
357
|
-
let res: Response;
|
|
358
|
-
try {
|
|
359
|
-
res = await doFetch(apiUrl, { headers, redirect: "error", signal: timeoutSignal(cfg.timeoutMs) });
|
|
360
|
-
} catch (err) {
|
|
361
|
-
throw new FetchError(`Request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
362
|
-
}
|
|
363
|
-
if (res.status >= 300 && res.status < 400) throw new FetchError(`Refusing to follow redirect from ${apiUrl}`);
|
|
364
|
-
if (res.status === 404) return [];
|
|
365
|
-
if (!res.ok) throw new FetchError(`${apiUrl} returned ${res.status}`);
|
|
366
|
-
const content = await res.text();
|
|
367
|
-
if (content.length > cfg.maxBytesPerFile || content.length > cfg.maxTotalBytes) {
|
|
368
|
-
throw new FetchError("Pipeline file exceeds the size cap.");
|
|
369
|
-
}
|
|
370
|
-
return [{ path: ".gitlab-ci.yml", content, lexicon: "gitlab" }];
|
|
371
|
-
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
4
|
+
import { join, relative } from "path";
|
|
5
|
+
import { fetchRepoFiles, FetchError } from "./fetch";
|
|
6
|
+
import { discoverByDetection, classifyFiles } from "./discover";
|
|
7
|
+
import { loadAuditPlugins } from "./discover";
|
|
8
|
+
|
|
9
|
+
const MIXED = fileURLToPath(new URL("../cli/commands/__fixtures__/audit-mixed", import.meta.url));
|
|
10
|
+
|
|
11
|
+
/** Read a dir into relative-path → content (the "repo" a mock will serve). */
|
|
12
|
+
function readDir(root: string): Record<string, string> {
|
|
13
|
+
const out: Record<string, string> = {};
|
|
14
|
+
const walk = (dir: string) => {
|
|
15
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
16
|
+
const full = join(dir, e.name);
|
|
17
|
+
if (e.isDirectory()) walk(full);
|
|
18
|
+
else out[relative(root, full)] = readFileSync(full, "utf-8");
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
walk(root);
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A GitHub-shaped mock: repo-info default_branch, recursive git/trees, base64 contents. */
|
|
26
|
+
function mockGithub(files: Record<string, string>): typeof fetch {
|
|
27
|
+
const b64 = (s: string) => Buffer.from(s, "utf-8").toString("base64");
|
|
28
|
+
return (async (url: string | URL | Request) => {
|
|
29
|
+
const u = String(url);
|
|
30
|
+
if (u.includes("/git/trees/")) {
|
|
31
|
+
const tree = Object.keys(files).map((path) => ({ path, type: "blob", size: files[path].length }));
|
|
32
|
+
return new Response(JSON.stringify({ tree }), { status: 200 });
|
|
33
|
+
}
|
|
34
|
+
const cm = u.match(/\/contents\/(.+?)\?/);
|
|
35
|
+
if (cm) {
|
|
36
|
+
const path = decodeURIComponent(cm[1]);
|
|
37
|
+
const content = files[path];
|
|
38
|
+
if (content === undefined) return new Response("not found", { status: 404 });
|
|
39
|
+
return new Response(JSON.stringify({ path, type: "file", content: b64(content), encoding: "base64" }), { status: 200 });
|
|
40
|
+
}
|
|
41
|
+
if (/\/repos\/[^/]+\/[^/]+(\?|$)/.test(u)) return new Response(JSON.stringify({ default_branch: "main" }), { status: 200 });
|
|
42
|
+
return new Response("not found", { status: 404 });
|
|
43
|
+
}) as unknown as typeof fetch;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const targets = (inputs: { lexicon: string; path: string }[]) => inputs.map((i) => `${i.lexicon}:${i.path}`).sort();
|
|
47
|
+
|
|
48
|
+
describe("remote audit covers all lexicons (not just CI)", () => {
|
|
49
|
+
test("a URL audit detects github + k8s + docker + aws in one repo", async () => {
|
|
50
|
+
const plugins = await loadAuditPlugins();
|
|
51
|
+
const files = await fetchRepoFiles("https://github.com/acme/widgets", { fetchImpl: mockGithub(readDir(MIXED)) });
|
|
52
|
+
const lexicons = new Set(classifyFiles(files, plugins).map((i) => i.lexicon));
|
|
53
|
+
expect([...lexicons].sort()).toEqual(["aws", "docker", "github", "k8s"]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("remote classification matches a local audit of the same repo (parity)", async () => {
|
|
57
|
+
const plugins = await loadAuditPlugins();
|
|
58
|
+
const local = discoverByDetection(MIXED, plugins);
|
|
59
|
+
const remote = classifyFiles(await fetchRepoFiles("https://github.com/acme/widgets", { fetchImpl: mockGithub(readDir(MIXED)) }), plugins);
|
|
60
|
+
expect(targets(remote)).toEqual(targets(local));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("caps the number of files fetched", async () => {
|
|
64
|
+
const files = await fetchRepoFiles("https://github.com/acme/widgets", { fetchImpl: mockGithub(readDir(MIXED)), maxFiles: 1 });
|
|
65
|
+
expect(files.length).toBe(1);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("rejects a non-allowlisted host before any fetch", async () => {
|
|
69
|
+
await expect(fetchRepoFiles("https://evil.example.com/o/r")).rejects.toThrow(/Host not allowed/);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("refuses to follow a redirect on the tree request", async () => {
|
|
73
|
+
const impl = (async (url: string | URL | Request) => {
|
|
74
|
+
const u = String(url);
|
|
75
|
+
if (u.includes("/git/trees/")) return new Response(null, { status: 302 });
|
|
76
|
+
return new Response(JSON.stringify({ default_branch: "main" }), { status: 200 });
|
|
77
|
+
}) as unknown as typeof fetch;
|
|
78
|
+
await expect(fetchRepoFiles("https://github.com/acme/widgets", { fetchImpl: impl })).rejects.toThrow(/redirect/i);
|
|
79
|
+
});
|
|
80
|
+
});
|
package/src/audit/report-html.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { AuditFinding } from "./core";
|
|
12
|
-
import { ruleDocUrl } from "./catalog";
|
|
12
|
+
import { ruleDocUrl, type RuleMeta } from "./catalog";
|
|
13
13
|
import { buildReportModel, buildReportJson, type AuditSnapshot, type BuildModelOptions, type EnrichedFinding, type GuidanceCluster, type QuickWinFile, type ReportCounts } from "./report-model";
|
|
14
14
|
|
|
15
15
|
export type { AuditSnapshot } from "./report-model";
|
|
@@ -34,7 +34,7 @@ export interface RenderHtmlOptions extends BuildModelOptions {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const DEFAULT_ACCENT = "#7c3aed";
|
|
37
|
-
const DEFAULT_TITLE = "
|
|
37
|
+
const DEFAULT_TITLE = "chant audit";
|
|
38
38
|
const DEFAULT_FOOTER = 'Generated by <a href="https://intentius.io/chant/cli/audit/">chant audit</a>.';
|
|
39
39
|
|
|
40
40
|
function esc(s: string): string {
|
|
@@ -46,6 +46,13 @@ function ruleLink(id: string): string {
|
|
|
46
46
|
return `<a class="rule-id" href="${esc(ruleDocUrl(id))}">${esc(id)}</a>`;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/** External backing for a merge-worthy rule (OSSF Scorecard, CIS, vendor docs). */
|
|
50
|
+
function authorityLinks(meta: RuleMeta): string {
|
|
51
|
+
if (!meta.authority?.length) return "";
|
|
52
|
+
const links = meta.authority.map((a) => `<a href="${esc(a.url)}">${esc(a.name)}</a>`).join(", ");
|
|
53
|
+
return ` <span class="muted">per ${links}</span>`;
|
|
54
|
+
}
|
|
55
|
+
|
|
49
56
|
function renderDiff(diff: string): string {
|
|
50
57
|
const rows = diff
|
|
51
58
|
.split("\n")
|
|
@@ -60,11 +67,11 @@ function renderDiff(diff: string): string {
|
|
|
60
67
|
function renderQuickWins(files: QuickWinFile[]): string {
|
|
61
68
|
const cards = files
|
|
62
69
|
.map((qw) => {
|
|
63
|
-
const chips = qw.addressed.map((m) => `<span class="chip">${ruleLink(m.id)} ${esc(m.title)}</span>`).join(" ");
|
|
70
|
+
const chips = qw.addressed.map((m) => `<span class="chip">${ruleLink(m.id)} ${esc(m.title)}${authorityLinks(m)}</span>`).join(" ");
|
|
64
71
|
const diff = qw.diff ? renderDiff(qw.diff) : "";
|
|
65
72
|
const needs = qw.needsInput.length
|
|
66
73
|
? `<div class="needs"><strong>Needs a value to auto-patch:</strong><ul>${qw.needsInput
|
|
67
|
-
.map((f) => `<li>${ruleLink(f.checkId)}${f.entity ? ` (<code>${esc(f.entity)}</code>)` : ""} — ${esc(f.meta.remediation)}</li>`)
|
|
74
|
+
.map((f) => `<li>${ruleLink(f.checkId)}${f.entity ? ` (<code>${esc(f.entity)}</code>)` : ""} — ${esc(f.meta.remediation)}${authorityLinks(f.meta)}</li>`)
|
|
68
75
|
.join("")}</ul></div>`
|
|
69
76
|
: "";
|
|
70
77
|
return `<div class="card"><div class="card-head"><code class="file">${esc(qw.file)}</code> ${chips}</div>${diff}${needs}</div>`;
|
|
@@ -82,7 +89,7 @@ function renderNeedsReview(clusters: GuidanceCluster[], n: number): string {
|
|
|
82
89
|
const locs = findings
|
|
83
90
|
.map((f) => `<li><code>${esc(f.file)}</code>${f.entity ? ` (<code>${esc(f.entity)}</code>)` : ""} — ${esc(f.message)}</li>`)
|
|
84
91
|
.join("");
|
|
85
|
-
return `<div class="rule"><div><span class="sev ${findings[0].severity}"></span><strong>${ruleLink(meta.id)}</strong> — ${esc(meta.title)}. <span class="muted">${esc(meta.remediation)}</span
|
|
92
|
+
return `<div class="rule"><div><span class="sev ${findings[0].severity}"></span><strong>${ruleLink(meta.id)}</strong> — ${esc(meta.title)}. <span class="muted">${esc(meta.remediation)}</span>${authorityLinks(meta)}</div><ul>${locs}</ul></div>`;
|
|
86
93
|
})
|
|
87
94
|
.join("");
|
|
88
95
|
return `<div class="cluster"><h3>${head}</h3>${rules}</div>`;
|
|
@@ -101,6 +108,7 @@ function renderReportOnly(findings: EnrichedFinding[], n: number): string {
|
|
|
101
108
|
function renderHeader(counts: ReportCounts, snapshot: AuditSnapshot | undefined, notes: string[]): string {
|
|
102
109
|
const sev = `<span class="sev error"></span>${counts.errors} error <span class="sev warning"></span>${counts.warnings} warning <span class="sev info"></span>${counts.infos} info`;
|
|
103
110
|
const tiers = `<span class="chip">${counts.quickWin} quick-win</span> <span class="chip">${counts.needsReview} needs-review</span> <span class="chip">${counts.reportOnly} hygiene</span>`;
|
|
111
|
+
const cats = `<span class="chip">${counts.security} security</span> <span class="chip">${counts.correctness} correctness</span> <span class="chip">${counts.bestPractice} best-practice</span>`;
|
|
104
112
|
const meta: string[] = [];
|
|
105
113
|
if (snapshot) {
|
|
106
114
|
if (snapshot.host) meta.push(esc(snapshot.host));
|
|
@@ -113,7 +121,7 @@ function renderHeader(counts: ReportCounts, snapshot: AuditSnapshot | undefined,
|
|
|
113
121
|
meta.push(`chant ${esc(snapshot.toolVersion)}`);
|
|
114
122
|
}
|
|
115
123
|
const noteHtml = notes.map((n) => `<div class="note">${esc(n)}</div>`).join("");
|
|
116
|
-
return `<div class="summary"><div class="meta">${meta.join(" · ")}</div><div class="counts">${sev}</div><div class="tiers">${tiers}</div>${noteHtml}</div>`;
|
|
124
|
+
return `<div class="summary"><div class="meta">${meta.join(" · ")}</div><div class="counts">${sev}</div><div class="tiers">${tiers}</div><div class="tiers">${cats}</div>${noteHtml}</div>`;
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
const STYLES = `
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { AuditFinding } from "./core";
|
|
9
|
-
import { RULE_CATALOG, ruleDocUrl, type Authority, type FixKind, type RuleMeta, type Tier } from "./catalog";
|
|
9
|
+
import { RULE_CATALOG, ruleDocUrl, type Authority, type Category, type FixKind, type RuleMeta, type Tier } from "./catalog";
|
|
10
10
|
import { proveFix, unifiedDiff, type ProveOptions } from "./proof";
|
|
11
11
|
import type { Severity } from "../lint/rule";
|
|
12
12
|
|
|
@@ -16,6 +16,8 @@ import type { Severity } from "../lint/rule";
|
|
|
16
16
|
* major. Consumers should check the major and ignore unknown fields. See
|
|
17
17
|
* `docs/cli/audit`.
|
|
18
18
|
*/
|
|
19
|
+
// Additive changes (new fields like `category`, #415) keep the same version per
|
|
20
|
+
// the stability contract; only renamed/removed fields bump it.
|
|
19
21
|
export const REPORT_SCHEMA_VERSION = "1.0";
|
|
20
22
|
|
|
21
23
|
export const SEVERITY_WEIGHT: Record<Severity, number> = { error: 0, warning: 1, info: 2 };
|
|
@@ -55,6 +57,10 @@ export interface ReportCounts {
|
|
|
55
57
|
errors: number;
|
|
56
58
|
warnings: number;
|
|
57
59
|
infos: number;
|
|
60
|
+
/** Findings by category (#415) — what kind of issue, not how confident the fix. */
|
|
61
|
+
security: number;
|
|
62
|
+
correctness: number;
|
|
63
|
+
bestPractice: number;
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
export interface ReportModel {
|
|
@@ -88,6 +94,8 @@ export interface SerializedFinding {
|
|
|
88
94
|
lexicon: string;
|
|
89
95
|
tier: Tier;
|
|
90
96
|
fixKind: FixKind;
|
|
97
|
+
/** What kind of finding this is (security / correctness / best-practice). */
|
|
98
|
+
category: Category;
|
|
91
99
|
title: string;
|
|
92
100
|
remediation: string;
|
|
93
101
|
authority: Authority[];
|
|
@@ -237,6 +245,9 @@ export function buildReportModel(findings: AuditFinding[], opts: BuildModelOptio
|
|
|
237
245
|
errors: shown.filter((f) => f.severity === "error").length,
|
|
238
246
|
warnings: shown.filter((f) => f.severity === "warning").length,
|
|
239
247
|
infos: shown.filter((f) => f.severity === "info").length,
|
|
248
|
+
security: shown.filter((f) => f.meta.category === "security").length,
|
|
249
|
+
correctness: shown.filter((f) => f.meta.category === "correctness").length,
|
|
250
|
+
bestPractice: shown.filter((f) => f.meta.category === "best-practice").length,
|
|
240
251
|
};
|
|
241
252
|
|
|
242
253
|
return {
|
|
@@ -266,6 +277,7 @@ export function buildReportJson(findings: AuditFinding[], opts: { snapshot?: Aud
|
|
|
266
277
|
lexicon: f.lexicon,
|
|
267
278
|
tier: f.meta.tier,
|
|
268
279
|
fixKind: f.meta.fixKind,
|
|
280
|
+
category: f.meta.category,
|
|
269
281
|
title: f.meta.title,
|
|
270
282
|
remediation: f.meta.remediation,
|
|
271
283
|
authority: f.meta.authority ?? [],
|
package/src/audit/report.test.ts
CHANGED
|
@@ -31,7 +31,9 @@ describe("renderMarkdown — reworked structure", () => {
|
|
|
31
31
|
test("quick wins show a real combined diff when file content is provided", () => {
|
|
32
32
|
const out = renderMarkdown(FINDINGS, { files: [{ path: ".github/workflows/ci.yml", content: CI }] });
|
|
33
33
|
expect(out).toContain("## Quick wins (deterministic)");
|
|
34
|
-
expect(out).toContain("Addresses [GHA033](https://intentius.io/chant/lint-rules/audit-rules/#gha033) (Blanket write-all permissions)
|
|
34
|
+
expect(out).toContain("Addresses [GHA033](https://intentius.io/chant/lint-rules/audit-rules/#gha033) (Blanket write-all permissions)");
|
|
35
|
+
// merge-worthy findings cite their external authority (#351 links surfaced in the layout)
|
|
36
|
+
expect(out).toContain("per [OSSF Scorecard — Token-Permissions](https://github.com/ossf/scorecard");
|
|
35
37
|
expect(out).toContain("```diff");
|
|
36
38
|
expect(out).toContain("-permissions: write-all");
|
|
37
39
|
expect(out).toContain("+permissions:");
|
package/src/audit/report.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { AuditFinding } from "./core";
|
|
14
|
-
import { ruleDocUrl } from "./catalog";
|
|
14
|
+
import { ruleDocUrl, type RuleMeta } from "./catalog";
|
|
15
15
|
import type { ProveOptions } from "./proof";
|
|
16
16
|
import { buildReportModel, type EnrichedFinding, type GuidanceCluster, type QuickWinFile } from "./report-model";
|
|
17
17
|
|
|
@@ -20,6 +20,12 @@ function ruleLink(id: string): string {
|
|
|
20
20
|
return `[${id}](${ruleDocUrl(id)})`;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** External backing for a merge-worthy rule (OSSF Scorecard, CIS, vendor docs). */
|
|
24
|
+
function authorityLinks(meta: RuleMeta): string {
|
|
25
|
+
if (!meta.authority?.length) return "";
|
|
26
|
+
return ` — per ${meta.authority.map((a) => `[${a.name}](${a.url})`).join(", ")}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
export interface RenderOptions {
|
|
24
30
|
/** Repo URL or path shown in the header. */
|
|
25
31
|
target?: string;
|
|
@@ -42,14 +48,14 @@ function renderQuickWins(files: QuickWinFile[]): string[] {
|
|
|
42
48
|
for (const qw of files) {
|
|
43
49
|
lines.push(`### \`${qw.file}\``);
|
|
44
50
|
if (qw.diff) {
|
|
45
|
-
const labels = qw.addressed.map((m) => `${ruleLink(m.id)} (${m.title})`).join(", ");
|
|
51
|
+
const labels = qw.addressed.map((m) => `${ruleLink(m.id)} (${m.title})${authorityLinks(m)}`).join(", ");
|
|
46
52
|
lines.push("", `Addresses ${labels}:`, "", "```diff", qw.diff, "```");
|
|
47
53
|
}
|
|
48
54
|
if (qw.needsInput.length > 0) {
|
|
49
55
|
lines.push("", "Needs a value before it can be auto-patched:");
|
|
50
56
|
for (const f of qw.needsInput) {
|
|
51
57
|
const where = f.entity ? ` (\`${f.entity}\`)` : "";
|
|
52
|
-
lines.push(`- **${ruleLink(f.checkId)}**${where} — ${f.meta.remediation}`);
|
|
58
|
+
lines.push(`- **${ruleLink(f.checkId)}**${where} — ${f.meta.remediation}${authorityLinks(f.meta)}`);
|
|
53
59
|
}
|
|
54
60
|
}
|
|
55
61
|
lines.push("");
|
|
@@ -62,7 +68,7 @@ function renderNeedsReview(clusters: GuidanceCluster[]): string[] {
|
|
|
62
68
|
for (const cluster of clusters) {
|
|
63
69
|
lines.push(`### ${cluster.url ? `[${cluster.name}](${cluster.url})` : cluster.name}`, "");
|
|
64
70
|
for (const { meta, findings } of cluster.rules) {
|
|
65
|
-
lines.push(`- **${ruleLink(meta.id)}** — ${meta.title} (${findings[0].severity}). ${meta.remediation}`);
|
|
71
|
+
lines.push(`- **${ruleLink(meta.id)}** — ${meta.title} (${findings[0].severity}). ${meta.remediation}${authorityLinks(meta)}`);
|
|
66
72
|
for (const f of findings) {
|
|
67
73
|
const where = f.entity ? `\`${f.file}\` (\`${f.entity}\`)` : `\`${f.file}\``;
|
|
68
74
|
lines.push(` - ${where} — ${escapeCell(f.message)}`);
|
|
@@ -87,7 +93,7 @@ export function renderMarkdown(findings: AuditFinding[], opts: RenderOptions = {
|
|
|
87
93
|
const model = buildReportModel(findings, opts);
|
|
88
94
|
const { counts } = model;
|
|
89
95
|
|
|
90
|
-
const lines: string[] = ["#
|
|
96
|
+
const lines: string[] = ["# chant audit"];
|
|
91
97
|
if (opts.target) lines.push("", `Target: ${opts.target}`);
|
|
92
98
|
lines.push("");
|
|
93
99
|
for (const note of opts.notes ?? []) lines.push(`> Note: ${note}`, "");
|
|
@@ -102,6 +108,8 @@ export function renderMarkdown(findings: AuditFinding[], opts: RenderOptions = {
|
|
|
102
108
|
`${counts.quickWin} quick-win, ${counts.needsReview} needs-review, ${counts.reportOnly} report-only ` +
|
|
103
109
|
`(${counts.errors} error, ${counts.warnings} warning, ${counts.infos} info).`,
|
|
104
110
|
"",
|
|
111
|
+
`By category: ${counts.security} security, ${counts.correctness} correctness, ${counts.bestPractice} best-practice.`,
|
|
112
|
+
"",
|
|
105
113
|
);
|
|
106
114
|
|
|
107
115
|
if (model.quickWins.length > 0) lines.push(...renderQuickWins(model.quickWins));
|
package/src/audit/rules-doc.ts
CHANGED
|
@@ -15,6 +15,10 @@ const GROUPS: Array<{ heading: string; prefixes: string[]; blurb: string }> = [
|
|
|
15
15
|
{ heading: "Forgejo (WFJ)", prefixes: ["WFJ"], blurb: "" },
|
|
16
16
|
{ heading: "Kubernetes (WK8 / ARGO)", prefixes: ["WK8", "ARGO"], blurb: "Run against Kubernetes manifests." },
|
|
17
17
|
{ heading: "Docker (DKRD)", prefixes: ["DKRD"], blurb: "Run against Dockerfiles and Compose files." },
|
|
18
|
+
{ heading: "AWS CloudFormation (WAW / COR / EXT)", prefixes: ["WAW", "COR", "EXT"], blurb: "Run against CloudFormation templates (JSON or YAML)." },
|
|
19
|
+
{ heading: "Azure ARM (AZR)", prefixes: ["AZR"], blurb: "Run against ARM deployment templates (JSON)." },
|
|
20
|
+
{ heading: "GCP Config Connector (WGC)", prefixes: ["WGC"], blurb: "Run against Config Connector (cnrm.cloud.google.com) manifests." },
|
|
21
|
+
{ heading: "Helm (WHM)", prefixes: ["WHM"], blurb: "Run against Helm charts (Chart.yaml + templates)." },
|
|
18
22
|
];
|
|
19
23
|
|
|
20
24
|
function ruleBlock(m: RuleMeta): string {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
|
|
3
|
+
"contentVersion": "1.0.0.0",
|
|
4
|
+
"resources": [
|
|
5
|
+
{
|
|
6
|
+
"type": "Microsoft.Storage/storageAccounts",
|
|
7
|
+
"apiVersion": "2022-09-01",
|
|
8
|
+
"name": "data",
|
|
9
|
+
"location": "eastus",
|
|
10
|
+
"properties": { "allowBlobPublicAccess": true }
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
image: myapp:latest
|