akm-cli 0.7.4 → 0.8.0-rc.10
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/CHANGELOG.md +224 -1
- package/README.md +22 -6
- package/SECURITY.md +93 -0
- package/dist/cli/config-migrate.js +144 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/parse-args.js +133 -0
- package/dist/cli/shared.js +129 -0
- package/dist/cli.js +2631 -1440
- package/dist/commands/add-cli.js +279 -0
- package/dist/commands/agent-dispatch.js +110 -0
- package/dist/commands/agent-support.js +68 -0
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +130 -534
- package/dist/commands/consolidate.js +2122 -0
- package/dist/commands/curate.js +45 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +660 -0
- package/dist/commands/distill.js +1081 -73
- package/dist/commands/env.js +213 -0
- package/dist/commands/eval-cases.js +43 -0
- package/dist/commands/events.js +15 -24
- package/dist/commands/extract-cli.js +127 -0
- package/dist/commands/extract-prompt.js +204 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/feedback-cli.js +331 -0
- package/dist/commands/graph.js +477 -0
- package/dist/commands/health.js +1302 -0
- package/dist/commands/help/help-accept.md +12 -0
- package/dist/commands/help/help-improve.md +69 -0
- package/dist/commands/help/help-proposals.md +18 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +11 -0
- package/dist/commands/history.js +54 -46
- package/dist/commands/improve-auto-accept.js +97 -0
- package/dist/commands/improve-cli.js +217 -0
- package/dist/commands/improve-profiles.js +166 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +2373 -0
- package/dist/commands/info.js +5 -2
- package/dist/commands/init.js +50 -2
- package/dist/commands/installed-stashes.js +102 -139
- package/dist/commands/knowledge.js +136 -0
- package/dist/commands/lint/agent-linter.js +49 -0
- package/dist/commands/lint/base-linter.js +479 -0
- package/dist/commands/lint/command-linter.js +49 -0
- package/dist/commands/lint/default-linter.js +16 -0
- package/dist/commands/lint/env-key-rules.js +154 -0
- package/dist/commands/lint/index.js +196 -0
- package/dist/commands/lint/knowledge-linter.js +16 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +61 -0
- package/dist/commands/lint/registry.js +36 -0
- package/dist/commands/lint/skill-linter.js +45 -0
- package/dist/commands/lint/task-linter.js +50 -0
- package/dist/commands/lint/types.js +4 -0
- package/dist/commands/lint/workflow-linter.js +56 -0
- package/dist/commands/lint.js +4 -0
- package/dist/commands/migration-help.js +3 -0
- package/dist/commands/proposal.js +67 -12
- package/dist/commands/propose.js +120 -45
- package/dist/commands/reflect.js +1104 -60
- package/dist/commands/registry-cli.js +150 -0
- package/dist/commands/registry-search.js +5 -2
- package/dist/commands/remember-cli.js +257 -0
- package/dist/commands/remember.js +70 -7
- package/dist/commands/schema-repair.js +203 -0
- package/dist/commands/search.js +115 -14
- package/dist/commands/secret.js +173 -0
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +158 -60
- package/dist/commands/source-add.js +17 -45
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +14 -19
- package/dist/commands/tasks.js +437 -0
- package/dist/commands/url-checker.js +42 -0
- package/dist/core/action-contributors.js +28 -0
- package/dist/core/asset-ref.js +17 -2
- package/dist/core/asset-registry.js +12 -17
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +67 -1
- package/dist/core/common.js +182 -0
- package/dist/core/concurrent.js +25 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +622 -0
- package/dist/core/config-schema.js +534 -0
- package/dist/core/config-sources.js +108 -0
- package/dist/core/config-types.js +4 -0
- package/dist/core/config-walker.js +337 -0
- package/dist/core/config.js +364 -968
- package/dist/core/errors.js +42 -20
- package/dist/core/events.js +105 -135
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +75 -8
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +20 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +806 -0
- package/dist/core/parse.js +158 -0
- package/dist/core/paths.js +280 -14
- package/dist/core/proposal-quality-validators.js +380 -0
- package/dist/core/proposal-validators.js +69 -0
- package/dist/core/proposals.js +512 -42
- package/dist/core/state-db.js +1068 -0
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +54 -0
- package/dist/core/tty.js +59 -0
- package/dist/core/warn.js +64 -1
- package/dist/core/write-source.js +3 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +198 -489
- package/dist/indexer/db.js +990 -108
- package/dist/indexer/ensure-index.js +136 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +376 -101
- package/dist/indexer/graph-db.js +391 -0
- package/dist/indexer/graph-dedup.js +95 -0
- package/dist/indexer/graph-extraction.js +550 -114
- package/dist/indexer/index-context.js +4 -0
- package/dist/indexer/indexer.js +547 -309
- package/dist/indexer/llm-cache.js +52 -0
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +167 -160
- package/dist/indexer/memory-inference.js +152 -74
- package/dist/indexer/metadata-contributors.js +29 -0
- package/dist/indexer/metadata.js +275 -196
- package/dist/indexer/path-resolver.js +92 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +331 -0
- package/dist/indexer/ranking.js +81 -0
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +111 -0
- package/dist/indexer/search-source.js +44 -10
- package/dist/indexer/semantic-status.js +6 -17
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +28 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +122 -230
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +7 -13
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +70 -5
- package/dist/integrations/agent/prompts.js +250 -36
- package/dist/integrations/agent/runner.js +151 -0
- package/dist/integrations/agent/sdk-runner.js +126 -0
- package/dist/integrations/agent/spawn.js +183 -35
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +32 -69
- package/dist/integrations/session-logs/index.js +69 -0
- package/dist/integrations/session-logs/inline-refs.js +35 -0
- package/dist/integrations/session-logs/pre-filter.js +152 -0
- package/dist/integrations/session-logs/providers/claude-code.js +282 -0
- package/dist/integrations/session-logs/providers/opencode.js +258 -0
- package/dist/integrations/session-logs/types.js +4 -0
- package/dist/llm/call-ai.js +62 -0
- package/dist/llm/client.js +79 -88
- package/dist/llm/embedder.js +20 -29
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +42 -1
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +95 -48
- package/dist/llm/graph-extract.js +676 -72
- package/dist/llm/index-passes.js +44 -29
- package/dist/llm/memory-infer.js +80 -71
- package/dist/llm/metadata-enhance.js +42 -29
- package/dist/llm/prompts/extract-session.md +80 -0
- package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/output/cli-hints-full.md +292 -0
- package/dist/output/cli-hints-short.md +66 -0
- package/dist/output/cli-hints.js +7 -311
- package/dist/output/context.js +60 -8
- package/dist/output/renderers.js +306 -258
- package/dist/output/shapes/curate.js +56 -0
- package/dist/output/shapes/distill.js +10 -0
- package/dist/output/shapes/env-list.js +19 -0
- package/dist/output/shapes/events.js +11 -0
- package/dist/output/shapes/helpers.js +424 -0
- package/dist/output/shapes/history.js +7 -0
- package/dist/output/shapes/passthrough.js +102 -0
- package/dist/output/shapes/proposal-accept.js +7 -0
- package/dist/output/shapes/proposal-diff.js +7 -0
- package/dist/output/shapes/proposal-list.js +7 -0
- package/dist/output/shapes/proposal-producer.js +11 -0
- package/dist/output/shapes/proposal-reject.js +7 -0
- package/dist/output/shapes/proposal-show.js +7 -0
- package/dist/output/shapes/registry-search.js +6 -0
- package/dist/output/shapes/registry.js +30 -0
- package/dist/output/shapes/search.js +6 -0
- package/dist/output/shapes/secret-list.js +19 -0
- package/dist/output/shapes/show.js +6 -0
- package/dist/output/shapes/vault-list.js +19 -0
- package/dist/output/shapes.js +51 -511
- package/dist/output/text/add.js +6 -0
- package/dist/output/text/clone.js +6 -0
- package/dist/output/text/config.js +6 -0
- package/dist/output/text/curate.js +6 -0
- package/dist/output/text/distill.js +7 -0
- package/dist/output/text/enable-disable.js +7 -0
- package/dist/output/text/events.js +10 -0
- package/dist/output/text/feedback.js +6 -0
- package/dist/output/text/helpers.js +1039 -0
- package/dist/output/text/history.js +7 -0
- package/dist/output/text/import.js +6 -0
- package/dist/output/text/index.js +6 -0
- package/dist/output/text/info.js +6 -0
- package/dist/output/text/init.js +6 -0
- package/dist/output/text/list.js +6 -0
- package/dist/output/text/proposal-producer.js +8 -0
- package/dist/output/text/proposal.js +11 -0
- package/dist/output/text/registry-commands.js +11 -0
- package/dist/output/text/registry.js +30 -0
- package/dist/output/text/remember.js +6 -0
- package/dist/output/text/remove.js +6 -0
- package/dist/output/text/save.js +6 -0
- package/dist/output/text/search.js +6 -0
- package/dist/output/text/show.js +6 -0
- package/dist/output/text/update.js +6 -0
- package/dist/output/text/upgrade.js +6 -0
- package/dist/output/text/vault.js +16 -0
- package/dist/output/text/wiki.js +15 -0
- package/dist/output/text/workflow.js +14 -0
- package/dist/output/text.js +44 -1093
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +4 -1
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +71 -50
- package/dist/registry/providers/static-index.js +53 -48
- package/dist/registry/providers/types.js +3 -24
- package/dist/registry/resolve.js +11 -16
- package/dist/registry/types.js +3 -0
- package/dist/scripts/migrate-storage.js +17750 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
- package/dist/scripts/migrations/v16-to-v17.js +141 -0
- package/dist/setup/detect.js +3 -0
- package/dist/setup/ripgrep-install.js +3 -0
- package/dist/setup/ripgrep-resolve.js +3 -0
- package/dist/setup/setup.js +775 -37
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +5 -12
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +179 -20
- package/dist/sources/providers/index.js +3 -0
- package/dist/sources/providers/install-types.js +3 -13
- package/dist/sources/providers/npm.js +3 -4
- package/dist/sources/providers/provider-utils.js +3 -0
- package/dist/sources/providers/sync-from-ref.js +3 -11
- package/dist/sources/providers/tar-utils.js +3 -0
- package/dist/sources/providers/website.js +18 -22
- package/dist/sources/resolve.js +3 -0
- package/dist/sources/types.js +3 -0
- package/dist/sources/website-ingest.js +7 -0
- package/dist/tasks/backends/cron.js +203 -0
- package/dist/tasks/backends/exec-utils.js +28 -0
- package/dist/tasks/backends/index.js +24 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +187 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +215 -0
- package/dist/tasks/parser.js +211 -0
- package/dist/tasks/resolveAkmBin.js +87 -0
- package/dist/tasks/runner.js +458 -0
- package/dist/tasks/schedule.js +227 -0
- package/dist/tasks/schema.js +15 -0
- package/dist/tasks/validator.js +62 -0
- package/dist/version.js +3 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +15 -0
- package/dist/wiki/wiki.js +13 -61
- package/dist/workflows/authoring.js +8 -25
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +141 -2
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +11 -3
- package/dist/workflows/runs.js +91 -89
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +79 -0
- package/dist/workflows/validator.js +4 -8
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +10 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +20 -0
- package/docs/migration/release-notes/0.8.0.md +48 -0
- package/docs/migration/v0.7-to-v0.8.md +1307 -0
- package/package.json +29 -11
- package/dist/commands/install-audit.js +0 -381
- package/dist/commands/vault.js +0 -333
- package/dist/templates/wiki-templates.js +0 -100
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Vault security lint rules — flags known-dangerous environment variable names.
|
|
6
|
+
*
|
|
7
|
+
* These env var names, when present as vault keys, indicate the vault can be
|
|
8
|
+
* used to hijack process execution via loader injection, path override, or
|
|
9
|
+
* shell/runtime startup hooks. The lint pass emits a warning-level finding;
|
|
10
|
+
* it does NOT block vault load or `akm vault setKey`.
|
|
11
|
+
*
|
|
12
|
+
* Enforcement scope:
|
|
13
|
+
* - `akm lint` reports findings as `dangerous-vault-key` (non-blocking warn).
|
|
14
|
+
* - `akm add` BLOCKS install unless `--allow-insecure` is set (or, on TTY,
|
|
15
|
+
* the user explicitly confirms at the prompt).
|
|
16
|
+
* - `akm vault setKey` does NOT consult this list — by design, the operator
|
|
17
|
+
* owns their own vault and may legitimately store any key locally. The
|
|
18
|
+
* gate exists only for third-party stash installation.
|
|
19
|
+
*
|
|
20
|
+
* False-positive tradeoff:
|
|
21
|
+
* A handful of keys (EDITOR, VISUAL, PAGER) are included because they are
|
|
22
|
+
* invoked by many interactive tools and are a documented RCE vector when
|
|
23
|
+
* sourced from untrusted vaults. They will also flag on benign vaults
|
|
24
|
+
* where the operator legitimately wants to set their editor — accept the
|
|
25
|
+
* FP and bypass with `--allow-insecure` after review.
|
|
26
|
+
*/
|
|
27
|
+
import { listKeys } from "../env";
|
|
28
|
+
// ── Dangerous key set ─────────────────────────────────────────────────────────
|
|
29
|
+
export const DANGEROUS_VAULT_KEYS = new Set([
|
|
30
|
+
// Dynamic linker hijacking (Linux glibc ld.so)
|
|
31
|
+
"LD_PRELOAD", // forces shared library injection
|
|
32
|
+
"LD_LIBRARY_PATH", // overrides library search path
|
|
33
|
+
"LD_AUDIT", // loads auditing libs (CVE-class injection vector)
|
|
34
|
+
"LD_DEBUG", // info disclosure / loader behaviour leak
|
|
35
|
+
"LD_BIND_NOW", // eager symbol resolution — can trigger malicious libs
|
|
36
|
+
"LD_PROFILE", // writes profile data — abusable for info disclosure
|
|
37
|
+
"LD_ASSUME_KERNEL", // kernel-version spoofing affecting loader behaviour
|
|
38
|
+
"LD_TRACE_LOADED_OBJECTS", // info disclosure (lists linked libs)
|
|
39
|
+
// Dynamic linker hijacking (macOS dyld)
|
|
40
|
+
"DYLD_INSERT_LIBRARIES", // macOS analogue of LD_PRELOAD
|
|
41
|
+
"DYLD_LIBRARY_PATH", // overrides dyld library search path
|
|
42
|
+
"DYLD_FRAMEWORK_PATH", // overrides framework search path
|
|
43
|
+
// Shell and command resolution
|
|
44
|
+
"PATH", // command lookup hijack
|
|
45
|
+
"BASH_ENV", // sourced on non-interactive bash startup (RCE)
|
|
46
|
+
"ENV", // sourced on POSIX sh startup (RCE)
|
|
47
|
+
"PROMPT_COMMAND", // command run before each bash prompt (RCE)
|
|
48
|
+
"PS1", // prompt — command substitution arbitrary code
|
|
49
|
+
"PS2", // continuation prompt — command substitution
|
|
50
|
+
"IFS", // Internal Field Separator — classic word-splitting attack
|
|
51
|
+
// Shell startup hijack
|
|
52
|
+
"ZDOTDIR", // zsh startup file lookup directory hijack
|
|
53
|
+
// Language runtime hijacking — Node.js
|
|
54
|
+
"NODE_OPTIONS", // injects flags incl. --require module-load RCE
|
|
55
|
+
"NODE_PATH", // module resolution hijack
|
|
56
|
+
"NODE_TLS_REJECT_UNAUTHORIZED", // silently disables TLS verification — MITM enabler
|
|
57
|
+
// Language runtime hijacking — Python
|
|
58
|
+
"PYTHONSTARTUP", // sourced by interactive python (RCE)
|
|
59
|
+
"PYTHONPATH", // module resolution hijack
|
|
60
|
+
"PYTHONINSPECT", // drops into REPL after script — sandbox escape vector
|
|
61
|
+
"PYTHONHOME", // python install prefix hijack
|
|
62
|
+
"PYTHONNOUSERSITE", // disables user-site isolation — sandbox weakening
|
|
63
|
+
// Language runtime hijacking — Ruby
|
|
64
|
+
"RUBYLIB", // ruby load path hijack
|
|
65
|
+
"RUBYOPT", // injects ruby command-line opts
|
|
66
|
+
// Language runtime hijacking — Perl
|
|
67
|
+
"PERL5LIB", // perl @INC hijack
|
|
68
|
+
"PERL5OPT", // injects perl command-line opts
|
|
69
|
+
// Language runtime hijacking — Java
|
|
70
|
+
"JAVA_TOOL_OPTIONS", // honoured by every JVM — flag injection / agent load
|
|
71
|
+
"JDK_JAVA_OPTIONS", // JDK launcher options injection
|
|
72
|
+
"_JAVA_OPTIONS", // legacy JVM options injection
|
|
73
|
+
// Git (RCE via git invocations)
|
|
74
|
+
"GIT_SSH_COMMAND", // replaces ssh with arbitrary command (RCE)
|
|
75
|
+
"GIT_EXTERNAL_DIFF", // runs arbitrary command during diff (RCE)
|
|
76
|
+
"GIT_PAGER", // runs arbitrary command for paging (RCE)
|
|
77
|
+
"GIT_EDITOR", // runs arbitrary command for editor (RCE)
|
|
78
|
+
// Interactive-tool invocation hijack — high FP rate but documented RCE vectors
|
|
79
|
+
"EDITOR", // invoked by git, crontab, sudoedit, etc. (RCE)
|
|
80
|
+
"VISUAL", // EDITOR fallback used by many tools (RCE)
|
|
81
|
+
"PAGER", // invoked by git, man, systemctl, etc. (RCE)
|
|
82
|
+
]);
|
|
83
|
+
/**
|
|
84
|
+
* Pattern-based dangerous key matchers.
|
|
85
|
+
*
|
|
86
|
+
* Some attack vectors target a family of variable names rather than a single
|
|
87
|
+
* literal — most famously Shellshock (CVE-2014-6271), which exploits keys
|
|
88
|
+
* prefixed with `BASH_FUNC_`. Listing every concrete name is impossible; we
|
|
89
|
+
* test against this pattern set in addition to the literal `Set`.
|
|
90
|
+
*/
|
|
91
|
+
export const DANGEROUS_VAULT_KEY_PATTERNS = [
|
|
92
|
+
{
|
|
93
|
+
// CVE-2014-6271 (Shellshock) — bash imports exported functions named
|
|
94
|
+
// `BASH_FUNC_<name>%%` and parses their bodies, enabling RCE.
|
|
95
|
+
pattern: /^BASH_FUNC_/,
|
|
96
|
+
reason: "Shellshock-class bash function injection (CVE-2014-6271)",
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
/**
|
|
100
|
+
* Returns `true` if the given key name is dangerous — either by literal match
|
|
101
|
+
* against `DANGEROUS_VAULT_KEYS` or by matching any entry in
|
|
102
|
+
* `DANGEROUS_VAULT_KEY_PATTERNS`.
|
|
103
|
+
*/
|
|
104
|
+
export function isDangerousVaultKey(key) {
|
|
105
|
+
if (DANGEROUS_VAULT_KEYS.has(key))
|
|
106
|
+
return true;
|
|
107
|
+
for (const { pattern } of DANGEROUS_VAULT_KEY_PATTERNS) {
|
|
108
|
+
if (pattern.test(key))
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
// ── Checker ───────────────────────────────────────────────────────────────────
|
|
114
|
+
/**
|
|
115
|
+
* Inspect a vault `.env` file and return a lint finding for every key whose
|
|
116
|
+
* name appears in `DANGEROUS_VAULT_KEYS` or matches a pattern in
|
|
117
|
+
* `DANGEROUS_VAULT_KEY_PATTERNS`.
|
|
118
|
+
*
|
|
119
|
+
* @param vaultPath Absolute path to the `.env` file.
|
|
120
|
+
* @param relPath Stash-relative path used as the `file` field in findings
|
|
121
|
+
* (e.g. `"vaults/prod.env"`).
|
|
122
|
+
* @param vaultRef Human-readable vault ref (e.g. `"vault:prod"`) shown in
|
|
123
|
+
* the finding message.
|
|
124
|
+
*/
|
|
125
|
+
export function checkVaultForDangerousKeys(vaultPath, relPath, vaultRef) {
|
|
126
|
+
const { keys } = listKeys(vaultPath);
|
|
127
|
+
const issues = [];
|
|
128
|
+
for (const key of keys) {
|
|
129
|
+
if (!isDangerousVaultKey(key))
|
|
130
|
+
continue;
|
|
131
|
+
issues.push({
|
|
132
|
+
file: relPath,
|
|
133
|
+
issue: "dangerous-vault-key",
|
|
134
|
+
detail: `Env key \`${key}\` can be used to hijack process execution when injected via \`akm env run\`. Ref: ${vaultRef}. Review this file before running \`akm env run\` commands against untrusted stashes.`,
|
|
135
|
+
fixed: false,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return issues;
|
|
139
|
+
}
|
|
140
|
+
// ── Env-neutral aliases ───────────────────────────────────────────────────────
|
|
141
|
+
//
|
|
142
|
+
// These primitives guard *environment variable injection*, which is shared by
|
|
143
|
+
// the `env` asset type, whole-file `secret` injection, and the `akm add`
|
|
144
|
+
// supply-chain gate. The original `*Vault*` names are retained above for
|
|
145
|
+
// backward compatibility (and stable lint output) through the 0.8.x
|
|
146
|
+
// deprecation window; new call sites should prefer the env-neutral names.
|
|
147
|
+
/** Env-neutral alias of {@link DANGEROUS_VAULT_KEYS}. */
|
|
148
|
+
export const DANGEROUS_ENV_KEYS = DANGEROUS_VAULT_KEYS;
|
|
149
|
+
/** Env-neutral alias of {@link DANGEROUS_VAULT_KEY_PATTERNS}. */
|
|
150
|
+
export const DANGEROUS_ENV_KEY_PATTERNS = DANGEROUS_VAULT_KEY_PATTERNS;
|
|
151
|
+
/** Env-neutral alias of {@link isDangerousVaultKey}. */
|
|
152
|
+
export const isDangerousEnvKey = isDangerousVaultKey;
|
|
153
|
+
/** Env-neutral alias of {@link checkVaultForDangerousKeys}. */
|
|
154
|
+
export const checkEnvForDangerousKeys = checkVaultForDangerousKeys;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { parse as parseYaml } from "yaml";
|
|
7
|
+
import { resolveStashDir } from "../../core/common";
|
|
8
|
+
import { loadConfig } from "../../core/config";
|
|
9
|
+
import { parseFrontmatter } from "../../core/frontmatter";
|
|
10
|
+
import { resolveSourceEntries } from "../../indexer/search-source";
|
|
11
|
+
import { checkVaultForDangerousKeys } from "./env-key-rules";
|
|
12
|
+
import { getLinterForType } from "./registry";
|
|
13
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
14
|
+
const STASH_SUBDIRS = [
|
|
15
|
+
"agents",
|
|
16
|
+
"commands",
|
|
17
|
+
"memories",
|
|
18
|
+
"skills",
|
|
19
|
+
"workflows",
|
|
20
|
+
"lessons",
|
|
21
|
+
"tasks",
|
|
22
|
+
"knowledge",
|
|
23
|
+
];
|
|
24
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
25
|
+
function collectYamlFiles(dir) {
|
|
26
|
+
if (!fs.existsSync(dir))
|
|
27
|
+
return [];
|
|
28
|
+
const results = [];
|
|
29
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
30
|
+
const full = path.join(dir, entry.name);
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
results.push(...collectYamlFiles(full));
|
|
33
|
+
}
|
|
34
|
+
else if (entry.isFile() && entry.name.endsWith(".yml")) {
|
|
35
|
+
results.push(full);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
40
|
+
function collectMarkdownFiles(dir) {
|
|
41
|
+
if (!fs.existsSync(dir))
|
|
42
|
+
return [];
|
|
43
|
+
const results = [];
|
|
44
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
45
|
+
const full = path.join(dir, entry.name);
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
results.push(...collectMarkdownFiles(full));
|
|
48
|
+
}
|
|
49
|
+
else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
50
|
+
results.push(full);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return results;
|
|
54
|
+
}
|
|
55
|
+
function collectEnvFiles(dir) {
|
|
56
|
+
const results = [];
|
|
57
|
+
try {
|
|
58
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
59
|
+
const full = path.join(dir, entry.name);
|
|
60
|
+
if (entry.isDirectory())
|
|
61
|
+
results.push(...collectEnvFiles(full));
|
|
62
|
+
else if (entry.isFile() && entry.name.endsWith(".env"))
|
|
63
|
+
results.push(full);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
/* dir may not exist */
|
|
68
|
+
}
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
71
|
+
/** True when the issue represents a file deletion that was successfully applied. */
|
|
72
|
+
function isFileDeletion(issue) {
|
|
73
|
+
return issue.fixed === true && (issue.issue === "orphaned-stub" || issue.issue === "placeholder-stub");
|
|
74
|
+
}
|
|
75
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
76
|
+
export function akmLint(options = {}) {
|
|
77
|
+
const stashRoot = options.dir ?? options.config?.stashDir ?? resolveStashDir();
|
|
78
|
+
// Collect secondary stash roots from configured filesystem sources so that
|
|
79
|
+
// cross-stash refs (e.g. referencing assets in dimm-city/agent-stash) are
|
|
80
|
+
// not falsely flagged as missing-ref.
|
|
81
|
+
const cfg = options.config ?? loadConfig();
|
|
82
|
+
const extraStashRoots = resolveSourceEntries(stashRoot, cfg)
|
|
83
|
+
.map((s) => s.path)
|
|
84
|
+
.filter((p) => p !== stashRoot && fs.existsSync(p));
|
|
85
|
+
const fix = options.fix ?? false;
|
|
86
|
+
const fixed = [];
|
|
87
|
+
const flagged = [];
|
|
88
|
+
for (const subdir of STASH_SUBDIRS) {
|
|
89
|
+
const dirPath = path.join(stashRoot, subdir);
|
|
90
|
+
// Tasks are .yml files; everything else is .md
|
|
91
|
+
const files = subdir === "tasks" ? collectYamlFiles(dirPath) : collectMarkdownFiles(dirPath);
|
|
92
|
+
const linter = getLinterForType(subdir);
|
|
93
|
+
// If the linter supports directory-level checks, run them for each direct
|
|
94
|
+
// subdirectory once before the per-file loop.
|
|
95
|
+
if (typeof linter.lintDirectory === "function" && fs.existsSync(dirPath)) {
|
|
96
|
+
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
|
|
97
|
+
if (entry.isDirectory()) {
|
|
98
|
+
const subdirIssues = linter.lintDirectory(path.join(dirPath, entry.name), stashRoot);
|
|
99
|
+
for (const issue of subdirIssues) {
|
|
100
|
+
if (issue.fixed) {
|
|
101
|
+
fixed.push(issue);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
flagged.push(issue);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const filePath of files) {
|
|
111
|
+
const relPath = path.relative(stashRoot, filePath);
|
|
112
|
+
let raw;
|
|
113
|
+
try {
|
|
114
|
+
raw = fs.readFileSync(filePath, "utf8");
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
let data;
|
|
120
|
+
let body;
|
|
121
|
+
let frontmatter;
|
|
122
|
+
if (subdir === "tasks") {
|
|
123
|
+
// Task files are pure YAML — parseFrontmatter returns empty data for them.
|
|
124
|
+
try {
|
|
125
|
+
const parsed = parseYaml(raw);
|
|
126
|
+
data =
|
|
127
|
+
parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
data = {};
|
|
131
|
+
}
|
|
132
|
+
body = raw;
|
|
133
|
+
frontmatter = null;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
({ data, content: body, frontmatter } = parseFrontmatter(raw));
|
|
137
|
+
}
|
|
138
|
+
const issues = linter.lint({ filePath, relPath, raw, data, body, frontmatter, fix, stashRoot, extraStashRoots });
|
|
139
|
+
let fileDeleted = false;
|
|
140
|
+
for (const issue of issues) {
|
|
141
|
+
if (isFileDeletion(issue)) {
|
|
142
|
+
fileDeleted = true;
|
|
143
|
+
fixed.push(issue);
|
|
144
|
+
}
|
|
145
|
+
else if (issue.fixed) {
|
|
146
|
+
fixed.push(issue);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
flagged.push(issue);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (fileDeleted)
|
|
153
|
+
continue; // file is gone — skip any remaining checks
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// ── Env dangerous-key pass ─────────────────────────────────────────────────
|
|
157
|
+
// Scan every `.env` file under <stashRoot>/env/ (and the deprecated
|
|
158
|
+
// <stashRoot>/vaults/) across all stash roots for keys that are known to
|
|
159
|
+
// enable process-execution hijacking. Warn-only — findings go into `flagged`,
|
|
160
|
+
// never `fixed`.
|
|
161
|
+
const envRoots = [stashRoot, ...extraStashRoots];
|
|
162
|
+
for (const root of envRoots) {
|
|
163
|
+
for (const [subdir, prefix] of [
|
|
164
|
+
["env", "env"],
|
|
165
|
+
["vaults", "vault"],
|
|
166
|
+
]) {
|
|
167
|
+
const dir = path.join(root, subdir);
|
|
168
|
+
if (!fs.existsSync(dir))
|
|
169
|
+
continue;
|
|
170
|
+
for (const envPath of collectEnvFiles(dir)) {
|
|
171
|
+
const baseName = path.basename(envPath, ".env");
|
|
172
|
+
// "default" (or empty) maps to ".env" → <prefix>:default
|
|
173
|
+
const ref = baseName === "" ? `${prefix}:default` : `${prefix}:${baseName}`;
|
|
174
|
+
const relPath = path.relative(root, envPath);
|
|
175
|
+
for (const issue of checkVaultForDangerousKeys(envPath, relPath, ref)) {
|
|
176
|
+
flagged.push(issue);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// `ok` reflects whether the lint run completed successfully — NOT whether
|
|
182
|
+
// it found anything. Findings are surfaced via `summary.flagged`; the CLI
|
|
183
|
+
// gates its exit code on `--fail-on-flagged`. Conflating "issues exist"
|
|
184
|
+
// with "command failed" caused two downstream problems:
|
|
185
|
+
// 1. `akm lint --json | jq …` saw stdout-flush races on Bun's non-zero
|
|
186
|
+
// exit, intermittently truncating the JSON the consumer read.
|
|
187
|
+
// 2. `ok` is the shared `{ok, error, code}` failure indicator across the
|
|
188
|
+
// whole CLI; reusing it for "found stuff" forced callers to disambiguate
|
|
189
|
+
// a successful-but-flagged run from a hard error by inspecting fields.
|
|
190
|
+
return {
|
|
191
|
+
ok: true,
|
|
192
|
+
fixed,
|
|
193
|
+
flagged,
|
|
194
|
+
summary: { fixed: fixed.length, flagged: flagged.length },
|
|
195
|
+
};
|
|
196
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { BaseLinter } from "./base-linter";
|
|
5
|
+
/**
|
|
6
|
+
* Linter for `knowledge/` assets.
|
|
7
|
+
*
|
|
8
|
+
* All checks are inherited from BaseLinter (`unquoted-colon`, `missing-updated`,
|
|
9
|
+
* `stale-path`, `missing-ref`). No type-specific rules needed.
|
|
10
|
+
*/
|
|
11
|
+
export class KnowledgeLinter extends BaseLinter {
|
|
12
|
+
types = ["knowledge"];
|
|
13
|
+
lint(ctx) {
|
|
14
|
+
return this.runBaseChecks(ctx);
|
|
15
|
+
}
|
|
16
|
+
}
|