akm-cli 0.9.1 → 0.9.2-alpha.2
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 +103 -28
- package/README.md +3 -1
- package/SECURITY.md +1 -1
- package/STABILITY.md +1 -1
- package/dist/akm +2 -2
- package/dist/akm-migrate +2 -2
- package/dist/assets/hints/cli-hints-full.md +14 -9
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/models.json +35 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
- package/dist/assets/tasks/core/extract.yml +6 -5
- package/dist/assets/tasks/core/improve.yml +6 -5
- package/dist/assets/tasks/core/index-refresh.yml +6 -5
- package/dist/assets/tasks/core/sync.yml +6 -5
- package/dist/assets/tasks/core/version-check.yml +6 -5
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
- package/dist/cli/confirm.js +2 -2
- package/dist/cli/parse-args.js +3 -24
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +2 -2
- package/dist/cli.js +11 -9
- package/dist/commands/agent/agent-dispatch.js +55 -89
- package/dist/commands/agent/contribute-cli.js +12 -45
- package/dist/commands/command/builtin-action.js +32 -0
- package/dist/commands/command/command-cli.js +99 -0
- package/dist/commands/command/command-execution.js +308 -0
- package/dist/commands/command/execution-source-loader.js +176 -0
- package/dist/commands/command/portable-template.js +60 -0
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env.js +4 -2
- package/dist/commands/feedback-cli.js +1 -1
- package/dist/commands/health/checks.js +241 -29
- package/dist/commands/health/html-report.js +0 -14
- package/dist/commands/health/report-view-model.js +0 -1
- package/dist/commands/health/surfaces.js +6 -7
- package/dist/commands/health/types.js +0 -2
- package/dist/commands/health.js +63 -18
- package/dist/commands/improve/collapse-detector.js +5 -6
- package/dist/commands/improve/consolidate.js +251 -214
- package/dist/commands/improve/distill/promote-memory.js +71 -34
- package/dist/commands/improve/distill/quality-gate.js +17 -5
- package/dist/commands/improve/distill.js +232 -155
- package/dist/commands/improve/eligibility.js +112 -79
- package/dist/commands/improve/execution.js +57 -0
- package/dist/commands/improve/extract-cli.js +5 -5
- package/dist/commands/improve/extract-prompt.js +64 -22
- package/dist/commands/improve/extract.js +608 -360
- package/dist/commands/improve/improve-strategies.js +43 -14
- package/dist/commands/improve/improve.js +249 -29
- package/dist/commands/improve/loop-stages.js +11 -17
- package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
- package/dist/commands/improve/outcome-loop.js +22 -38
- package/dist/commands/improve/planner.js +134 -0
- package/dist/commands/improve/preparation.js +730 -409
- package/dist/commands/improve/reflect.js +386 -223
- package/dist/commands/improve/run-context.js +3 -4
- package/dist/commands/improve/salience.js +6 -58
- package/dist/commands/improve/session-asset.js +12 -12
- package/dist/commands/lint/index.js +101 -29
- package/dist/commands/migrate-cli.js +11 -69
- package/dist/commands/migration-tool.js +6 -9
- package/dist/commands/models-cli.js +27 -0
- package/dist/commands/proposal/drain.js +258 -186
- package/dist/commands/proposal/proposal-cli.js +32 -10
- package/dist/commands/proposal/proposal.js +2 -5
- package/dist/commands/proposal/propose.js +192 -172
- package/dist/commands/proposal/repository.js +54 -91
- package/dist/commands/proposal/validators/proposal-validators.js +9 -7
- package/dist/commands/read/curate.js +53 -22
- package/dist/commands/read/registry-search.js +25 -9
- package/dist/commands/read/remember-cli.js +14 -2
- package/dist/commands/read/search.js +10 -4
- package/dist/commands/read/show.js +139 -153
- package/dist/commands/registry-cli.js +16 -7
- package/dist/commands/remember.js +33 -18
- package/dist/commands/sources/add-cli.js +19 -178
- package/dist/commands/sources/bundle-cli.js +15 -3
- package/dist/commands/sources/dangerous-env-audit.js +135 -0
- package/dist/commands/sources/info.js +2 -1
- package/dist/commands/sources/installed-stashes.js +901 -177
- package/dist/commands/sources/schema-repair.js +174 -95
- package/dist/commands/sources/self-update.js +30 -74
- package/dist/commands/sources/source-add.js +3 -5
- package/dist/commands/sources/sources-cli.js +2 -15
- package/dist/commands/sources/update-transaction.js +220 -0
- package/dist/commands/tasks/tasks-cli.js +3 -3
- package/dist/commands/tasks/tasks.js +736 -317
- package/dist/commands/workflow-cli.js +2 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
- package/dist/core/adapter/adapters/akm-adapter.js +85 -35
- package/dist/core/adapter/adapters/akm-lint.js +54 -39
- package/dist/core/adapter/adapters/akm-metadata.js +45 -45
- package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
- package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
- package/dist/core/adapter/adapters/index.js +0 -9
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
- package/dist/core/adapter/adapters/okf-adapter.js +4 -0
- package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
- package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
- package/dist/core/adapter/execution-source.js +308 -0
- package/dist/core/adapter/recognize-match.js +36 -13
- package/dist/core/adapter/registry.js +0 -9
- package/dist/core/asset/stash-meta.js +94 -4
- package/dist/core/common.js +6 -11
- package/dist/core/config/config-io.js +3 -3
- package/dist/core/config/config-schema.js +18 -40
- package/dist/core/config/config-sources.js +11 -21
- package/dist/core/config/config-walker.js +31 -13
- package/dist/core/config/config.js +23 -26
- package/dist/core/config/schema/engines.js +8 -7
- package/dist/core/config/schema/improve-processes.js +29 -5
- package/dist/core/config/schema/index-config.js +0 -27
- package/dist/core/config/schema/primitives.js +1 -23
- package/dist/core/config/schema/sources-bundles.js +13 -16
- package/dist/core/errors.js +2 -0
- package/dist/core/events.js +68 -32
- package/dist/core/extra-params.js +1 -0
- package/dist/core/improve-result.js +315 -0
- package/dist/core/lesson-lint.js +0 -6
- package/dist/core/maintenance-barrier.js +4 -4
- package/dist/core/network-policy.js +152 -0
- package/dist/core/paths.js +1 -1
- package/dist/core/recognition-util.js +4 -4
- package/dist/core/registry-url.js +456 -0
- package/dist/core/state/migrations.js +161 -47
- package/dist/core/state-db.js +453 -80
- package/dist/core/system-error.js +32 -0
- package/dist/core/time.js +2 -12
- package/dist/core/write-source.js +0 -18
- package/dist/execution/directory-identity.js +52 -0
- package/dist/execution/executable-identity.js +107 -0
- package/dist/execution/guarded-source.js +398 -0
- package/dist/execution/json.js +95 -0
- package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
- package/dist/execution/record.js +55 -0
- package/dist/execution/resolved-request.js +730 -0
- package/dist/execution/source.js +320 -0
- package/dist/indexer/bundle-identity-guard.js +5 -4
- package/dist/indexer/db/graph-db.js +33 -0
- package/dist/indexer/graph/graph-boost.js +3 -4
- package/dist/indexer/graph/graph-extraction.js +562 -373
- package/dist/indexer/index-written-assets.js +78 -39
- package/dist/indexer/indexer.js +471 -432
- package/dist/indexer/installations.js +6 -0
- package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
- package/dist/indexer/materialize-embeddings.js +155 -0
- package/dist/indexer/passes/memory-inference.js +227 -174
- package/dist/indexer/passes/metadata.js +263 -118
- package/dist/indexer/scan/doc-to-entry.js +7 -10
- package/dist/indexer/scan/drain-dir.js +51 -23
- package/dist/indexer/search/db-search.js +156 -50
- package/dist/indexer/search/fts-query.js +40 -40
- package/dist/indexer/search/ranking.js +36 -1
- package/dist/indexer/search/search-attribution.js +3 -1
- package/dist/indexer/search/search-fields.js +23 -14
- package/dist/indexer/search/search-hit-enrichers.js +1 -1
- package/dist/indexer/search/search-source.js +7 -16
- package/dist/indexer/search/semantic-status.js +10 -1
- package/dist/indexer/usage/show-usage.js +105 -0
- package/dist/indexer/usage/usage-events.js +7 -2
- package/dist/indexer/walk/matchers.js +40 -10
- package/dist/indexer/walk/path-resolver.js +5 -2
- package/dist/indexer/walk/walker.js +20 -2
- package/dist/integrations/agent/builder-shared.js +3 -6
- package/dist/integrations/agent/conversation-fallback.js +16 -0
- package/dist/integrations/agent/engine-resolution.js +87 -87
- package/dist/integrations/agent/execution-cascade.js +566 -0
- package/dist/integrations/agent/execution-definitions.js +211 -0
- package/dist/integrations/agent/execution-lowering.js +811 -0
- package/dist/integrations/agent/execution-preparation.js +67 -0
- package/dist/integrations/agent/index.js +0 -2
- package/dist/integrations/agent/inline-execution.js +74 -0
- package/dist/integrations/agent/model-map.js +515 -0
- package/dist/integrations/agent/persona-fallback.js +30 -0
- package/dist/integrations/agent/request-lowering.js +186 -0
- package/dist/integrations/agent/runner-dispatch.js +230 -37
- package/dist/integrations/agent/runner.js +12 -83
- package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
- package/dist/integrations/harnesses/aider/index.js +0 -1
- package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
- package/dist/integrations/harnesses/amazonq/index.js +0 -1
- package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
- package/dist/integrations/harnesses/claude/index.js +1 -5
- package/dist/integrations/harnesses/claude/session-log.js +3 -33
- package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
- package/dist/integrations/harnesses/codex/index.js +0 -1
- package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
- package/dist/integrations/harnesses/copilot/index.js +0 -1
- package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
- package/dist/integrations/harnesses/gemini/index.js +0 -1
- package/dist/integrations/harnesses/index.js +4 -44
- package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
- package/dist/integrations/harnesses/opencode/index.js +0 -2
- package/dist/integrations/harnesses/opencode/session-log.js +14 -204
- package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
- package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
- package/dist/integrations/harnesses/openhands/index.js +0 -1
- package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
- package/dist/integrations/harnesses/pi/index.js +0 -1
- package/dist/integrations/harnesses/shared.js +0 -1
- package/dist/integrations/harnesses/types.js +1 -3
- package/dist/integrations/lockfile.js +82 -79
- package/dist/integrations/session-logs/index.js +6 -17
- package/dist/integrations/session-logs/provider-base.js +1 -29
- package/dist/llm/client.js +10 -5
- package/dist/llm/embedder.js +6 -7
- package/dist/llm/embedders/local.js +37 -88
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +75 -50
- package/dist/llm/index-passes.js +43 -5
- package/dist/llm/memory-infer.js +8 -6
- package/dist/llm/metadata-enhance.js +5 -3
- package/dist/llm/structured-call.js +122 -25
- package/dist/output/format-exempt.js +1 -1
- package/dist/output/render-registry.js +0 -16
- package/dist/output/renderers.js +12 -7
- package/dist/output/shapes/curate.js +1 -0
- package/dist/output/shapes/helpers.js +10 -2
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/command-format.js +31 -33
- package/dist/output/text/health-format.js +1 -29
- package/dist/output/text/migrate.js +6 -56
- package/dist/output/text/proposal-format.js +16 -1
- package/dist/output/text/workflow-format.js +16 -0
- package/dist/registry/network.js +279 -0
- package/dist/registry/pinned-request-helper.js +247 -0
- package/dist/registry/pinned-transport.js +717 -0
- package/dist/registry/providers/skills-sh.js +18 -6
- package/dist/registry/providers/static-index.js +20 -7
- package/dist/registry/resolve.js +53 -28
- package/dist/scripts/akm-migrate-node.js +19334 -52269
- package/dist/scripts/akm-migrate.js +19270 -51612
- package/dist/setup/registry-stash-loader.js +64 -20
- package/dist/setup/semantic-assets.js +9 -34
- package/dist/setup/setup.js +12 -30
- package/dist/setup/source-identity.js +17 -0
- package/dist/setup/steps/sources.js +36 -15
- package/dist/setup/steps/tasks.js +39 -11
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/providers/npm.js +2 -2
- package/dist/sources/providers/provider-utils.js +4 -3
- package/dist/sources/providers/website.js +11 -7
- package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
- package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
- package/dist/sources/website-url.js +73 -0
- package/dist/storage/engines/sqlite-migrations.js +81 -26
- package/dist/storage/managed-db.js +27 -24
- package/dist/storage/repositories/events-repository.js +3 -0
- package/dist/storage/repositories/index-connection.js +42 -10
- package/dist/storage/repositories/index-entries-repository.js +203 -229
- package/dist/storage/repositories/index-entry-mapper.js +8 -12
- package/dist/storage/repositories/index-entry-schema.js +255 -0
- package/dist/storage/repositories/index-fts-repository.js +64 -71
- package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
- package/dist/storage/repositories/index-meta-repository.js +0 -11
- package/dist/storage/repositories/index-schema.js +74 -350
- package/dist/storage/repositories/index-utility-repository.js +12 -17
- package/dist/storage/repositories/index-vec-repository.js +56 -7
- package/dist/storage/repositories/proposals-repository.js +4 -127
- package/dist/storage/repositories/registry-cache.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +20 -40
- package/dist/storage/repositories/workflow-runs-repository.js +228 -129
- package/dist/storage/sqlite-read-snapshot.js +148 -0
- package/dist/tasks/backends/cron.js +170 -42
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +787 -202
- package/dist/tasks/backends/schtasks.js +282 -83
- package/dist/tasks/embedded.js +7 -7
- package/dist/tasks/frozen-script.js +50 -0
- package/dist/tasks/resolve-akm-bin.js +5 -1
- package/dist/tasks/runner.js +239 -251
- package/dist/tasks/runtime-v3.js +281 -0
- package/dist/tasks/scheduler-binding.js +272 -0
- package/dist/tasks/scheduler-invocation.js +57 -43
- package/dist/tasks/scheduler-sync.js +654 -0
- package/dist/tasks/source-v3.js +752 -0
- package/dist/tasks/standalone-script-entry.js +5 -0
- package/dist/tasks/task-id.js +29 -0
- package/dist/workflows/authoring/authoring.js +15 -32
- package/dist/workflows/exec/dispatch-redaction.js +14 -8
- package/dist/workflows/exec/exec-unit.js +7 -28
- package/dist/workflows/exec/frozen-judge.js +57 -89
- package/dist/workflows/exec/lowering-notices.js +23 -0
- package/dist/workflows/exec/native-executor.js +301 -458
- package/dist/workflows/exec/param-secrets.js +4 -3
- package/dist/workflows/exec/run-workflow.js +26 -32
- package/dist/workflows/exec/step-work.js +105 -109
- package/dist/workflows/exec/unit-dispatch.js +103 -27
- package/dist/workflows/exec/unit-writer.js +3 -3
- package/dist/workflows/exec/worktree.js +2 -2
- package/dist/workflows/ir/compile.js +86 -72
- package/dist/workflows/ir/environment-v4.js +328 -0
- package/dist/workflows/ir/freeze-v4.js +122 -0
- package/dist/workflows/ir/plan-hash.js +13 -7
- package/dist/workflows/ir/schema-v4.js +525 -0
- package/dist/workflows/ir/schema.js +25 -284
- package/dist/workflows/ir/source-freeze-v4.js +506 -0
- package/dist/workflows/parser.js +27 -24
- package/dist/workflows/program/schema.js +1 -2
- package/dist/workflows/renderer.js +42 -29
- package/dist/workflows/resource-limits.js +4 -5
- package/dist/workflows/runtime/agent-identity.js +11 -13
- package/dist/workflows/runtime/plan-classifier.js +8 -8
- package/dist/workflows/runtime/runs.js +27 -43
- package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
- package/dist/workflows/source-files.js +373 -0
- package/dist/workflows/source-ir/compile.js +196 -0
- package/dist/workflows/source-ir/github-yaml.js +577 -0
- package/dist/workflows/source-ir/ordering.js +38 -0
- package/dist/workflows/source-ir/program.js +50 -0
- package/dist/workflows/source-ir/result.js +26 -0
- package/dist/workflows/source-ir/schema.js +772 -0
- package/dist/workflows/source-ir/semantics.js +242 -0
- package/dist/workflows/source-ir/uses.js +14 -0
- package/docs/README.md +2 -0
- package/docs/migration/README.md +3 -1
- package/docs/migration/release-notes/0.9.2.md +55 -0
- package/docs/migration/release-notes/README.md +5 -0
- package/docs/migration/v0.8-to-v0.9.md +76 -1077
- package/docs/migration/v0.9.0-troubleshooting.md +104 -516
- package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
- package/docs/reference/README.md +1 -0
- package/docs/reference/cli.md +230 -98
- package/docs/reference/configuration.md +159 -36
- package/docs/reference/data-and-telemetry.md +19 -1
- package/docs/reference/supported-formats.md +23 -3
- package/docs/reference/tasks.md +182 -0
- package/docs/reference/workflow-schema.md +91 -40
- package/docs/reference/workflows.md +33 -6
- package/package.json +10 -6
- package/schemas/akm-config.json +372 -224
- package/schemas/akm-task.json +324 -80
- package/schemas/akm-workflow.json +6 -9
- package/dist/core/migration-operation.js +0 -75
- package/dist/integrations/agent/model-aliases.js +0 -74
- package/dist/tasks/parser.js +0 -380
- package/dist/tasks/schema.js +0 -123
- package/dist/tasks/validator.js +0 -80
- package/dist/workflows/ir/freeze.js +0 -320
- package/dist/workflows/runtime/document-cache.js +0 -13
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
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 * as p from "../../cli/clack.js";
|
|
7
4
|
import { getStringArg, parsePositiveIntFlag } from "../../cli/parse-args.js";
|
|
8
5
|
import { defineJsonCommand, output } from "../../cli/shared.js";
|
|
9
|
-
import { decideDangerousKeyInstall } from "../../core/activation-policy.js";
|
|
10
6
|
import { UsageError } from "../../core/errors.js";
|
|
11
7
|
import { appendEvent } from "../../core/events.js";
|
|
12
8
|
import { warn } from "../../core/warn.js";
|
|
13
|
-
import {
|
|
9
|
+
import { auditStashForDangerousKeys } from "./dangerous-env-audit.js";
|
|
14
10
|
import { akmRemove } from "./installed-stashes.js";
|
|
15
11
|
import { akmAdd } from "./source-add.js";
|
|
16
12
|
import { addStash } from "./source-manage.js";
|
|
@@ -39,185 +35,30 @@ export function shouldWarnOnPlainHttp(ref) {
|
|
|
39
35
|
return true;
|
|
40
36
|
}
|
|
41
37
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Recursively enumerate every real env FILE under `rootDir` (a stash's
|
|
44
|
-
* `env/` directory). "Real env file" is the SAME test used everywhere else
|
|
45
|
-
* in akm decides what `akm env run` / `akm env list` / the indexer will
|
|
46
|
-
* actually load as environment variables — `fileName === ".env" ||
|
|
47
|
-
* fileName.endsWith(".env")` — see `src/indexer/walk/matchers.ts` and
|
|
48
|
-
* `listEnvsRecursive` in `src/commands/env/env-cli.ts`. Reusing that exact
|
|
49
|
-
* rule here (rather than inventing a second, possibly-divergent one) is what
|
|
50
|
-
* closes the nested-directory bypass: `env/nested/inner.env` IS loaded by
|
|
51
|
-
* `akm env run` today via that same recursive walk, so it must be scanned
|
|
52
|
-
* too (previously only a flat, non-recursive `env/*.env` listing was
|
|
53
|
-
* scanned).
|
|
54
|
-
*
|
|
55
|
-
* Deliberately excludes files that do NOT end in `.env` (e.g.
|
|
56
|
-
* `env/notes.txt`). Such a file is never sourced as environment variables by
|
|
57
|
-
* any akm codepath, so a dangerous key sitting in its contents cannot hijack
|
|
58
|
-
* process execution via `akm env run` — there is no live attack through this
|
|
59
|
-
* gate for it. Residual gap: this scanner does not protect against an
|
|
60
|
-
* operator later renaming/copying that file to a `*.env` name, or feeding it
|
|
61
|
-
* into `akm env run` through some other mechanism outside akm; closing that
|
|
62
|
-
* would mean content-sniffing every byte of every file in the stash (README,
|
|
63
|
-
* lockfiles, binaries, …) for a vector that requires a subsequent human
|
|
64
|
-
* action to become live, which is a materially different and much more
|
|
65
|
-
* expensive check than "is this a file `akm` will actually source".
|
|
66
|
-
*/
|
|
67
|
-
function collectEnvFilePathsRecursive(rootDir) {
|
|
68
|
-
const results = [];
|
|
69
|
-
const walk = (dir) => {
|
|
70
|
-
let entries;
|
|
71
|
-
try {
|
|
72
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
for (const entry of entries) {
|
|
78
|
-
const full = path.join(dir, entry.name);
|
|
79
|
-
if (entry.isDirectory()) {
|
|
80
|
-
walk(full);
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (!entry.isFile())
|
|
84
|
-
continue;
|
|
85
|
-
if (entry.name !== ".env" && !entry.name.endsWith(".env"))
|
|
86
|
-
continue;
|
|
87
|
-
results.push(full);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
walk(rootDir);
|
|
91
|
-
return results;
|
|
92
|
-
}
|
|
93
|
-
/** Scan every real env file (recursively, see {@link collectEnvFilePathsRecursive}) in the freshly-installed stash for dangerous env keys. */
|
|
94
|
-
function collectDangerousKeyFindings(installedStashRoot, checkEnvForDangerousKeys) {
|
|
95
|
-
const allFindings = [];
|
|
96
|
-
const subdir = "env";
|
|
97
|
-
const prefix = "env";
|
|
98
|
-
const dir = path.join(installedStashRoot, subdir);
|
|
99
|
-
if (!fs.existsSync(dir))
|
|
100
|
-
return allFindings;
|
|
101
|
-
for (const envPath of collectEnvFilePathsRecursive(dir)) {
|
|
102
|
-
const relToEnvDir = path.relative(dir, envPath);
|
|
103
|
-
// Only the LAST path segment carries the ".env" suffix / dotfile-default
|
|
104
|
-
// naming rule; intermediate directory segments pass through unchanged.
|
|
105
|
-
// e.g. "nested/inner.env" -> "nested/inner", "nested/.env" ->
|
|
106
|
-
// "nested/default" (not "nested/", which is what naively stripping the
|
|
107
|
-
// suffix from the whole relative path would produce).
|
|
108
|
-
const segments = relToEnvDir.split(path.sep);
|
|
109
|
-
const lastSegment = segments[segments.length - 1] ?? "";
|
|
110
|
-
const lastSegmentBase = lastSegment.endsWith(".env") ? lastSegment.slice(0, -".env".length) : lastSegment;
|
|
111
|
-
const refSegments = [...segments.slice(0, -1), lastSegmentBase === "" ? "default" : lastSegmentBase];
|
|
112
|
-
const envRef = `${prefix}/${refSegments.join("/")}`;
|
|
113
|
-
const relPath = path.join(subdir, relToEnvDir);
|
|
114
|
-
const findings = checkEnvForDangerousKeys(envPath, relPath, envRef);
|
|
115
|
-
for (const finding of findings) {
|
|
116
|
-
// Extract the key name from the detail string for the summary line.
|
|
117
|
-
const keyMatch = finding.detail.match(/Env key `([^`]+)`/);
|
|
118
|
-
const keyName = keyMatch ? keyMatch[1] : finding.file;
|
|
119
|
-
allFindings.push({ envRef, keyName, relPath });
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return allFindings;
|
|
123
|
-
}
|
|
124
38
|
/**
|
|
125
39
|
* Audit a freshly-installed stash for dangerous env keys and decide whether the
|
|
126
|
-
* install must be blocked.
|
|
127
|
-
* `
|
|
128
|
-
* block comment above for the security rationale.
|
|
40
|
+
* install must be blocked. The scanner and decision policy are shared with
|
|
41
|
+
* `bundle update` in dangerous-env-audit.ts.
|
|
129
42
|
*/
|
|
130
43
|
export async function auditInstalledStashForDangerousKeys(opts) {
|
|
131
44
|
const { installedStashRoot, ref, allowDangerousKeys, rollbackTarget, isTTY } = opts;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return { blocked: false };
|
|
143
|
-
}
|
|
144
|
-
// The workspace activation policy fixes the baseline stance; the interactive
|
|
145
|
-
// confirm / rollback below is layered on top of the `"gate"` stance only.
|
|
146
|
-
const stance = decideDangerousKeyInstall({
|
|
147
|
-
findingsPresent: allFindings.length > 0,
|
|
148
|
-
allowInsecure: allowDangerousKeys,
|
|
149
|
-
});
|
|
150
|
-
if (stance === "allow")
|
|
151
|
-
return { blocked: false };
|
|
152
|
-
if (stance === "warn-allow") {
|
|
153
|
-
// Operator has explicitly accepted the risk — warn and continue.
|
|
154
|
-
for (const f of allFindings) {
|
|
155
|
-
warn(`[dangerous-env-key] ${f.relPath}: key \`${f.keyName}\` in ${f.envRef} can hijack process execution via \`akm env run\`. Proceeding because --allow-insecure was set.`);
|
|
156
|
-
}
|
|
157
|
-
return { blocked: false };
|
|
158
|
-
}
|
|
159
|
-
// Helper: roll the install back before aborting. Rollback is best-effort; a
|
|
160
|
-
// failed rollback never UN-blocks the install — we still abort, just with a
|
|
161
|
-
// warning telling the operator to remove the stash manually.
|
|
162
|
-
async function rollback() {
|
|
163
|
-
try {
|
|
164
|
-
await akmRemove({ target: rollbackTarget });
|
|
165
|
-
return undefined;
|
|
166
|
-
}
|
|
167
|
-
catch (_rollbackErr) {
|
|
168
|
-
return (`Rollback failed — stash may still be installed at ${installedStashRoot}. ` +
|
|
169
|
-
`Remove it manually with: akm bundle remove ${rollbackTarget}`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
if (isTTY) {
|
|
173
|
-
// Interactive path: show findings and ask the user to confirm.
|
|
174
|
-
// Guard on stdin (not stdout) because p.confirm() reads from stdin;
|
|
175
|
-
// stdout may be a TTY while stdin is piped, which would cause a hang.
|
|
176
|
-
const stashLabel = ref;
|
|
177
|
-
const groupedByEnv = new Map();
|
|
178
|
-
for (const f of allFindings) {
|
|
179
|
-
const existing = groupedByEnv.get(f.envRef) ?? [];
|
|
180
|
-
existing.push(f.keyName);
|
|
181
|
-
groupedByEnv.set(f.envRef, existing);
|
|
182
|
-
}
|
|
183
|
-
for (const [envRef, keys] of groupedByEnv) {
|
|
184
|
-
// envRef and keys come from filenames and KEY names inside a downloaded or
|
|
185
|
-
// cloned bundle, i.e. attacker-controllable. Tar validation rejects NUL but
|
|
186
|
-
// not ESC/CSI, and git checkout allows them in filenames on Linux/macOS —
|
|
187
|
-
// so printing them raw let a crafted bundle rewrite this security prompt
|
|
188
|
-
// with terminal escapes right before an "Install anyway?" confirmation.
|
|
189
|
-
warn(`[warn] Env "${sanitizeString(envRef)}" in stash "${sanitizeString(stashLabel)}" contains potentially dangerous keys:`);
|
|
190
|
-
for (const key of keys) {
|
|
191
|
-
warn(` - ${sanitizeString(key)}: can hijack process execution via \`akm env run\``);
|
|
45
|
+
return auditStashForDangerousKeys({
|
|
46
|
+
stashRoot: installedStashRoot,
|
|
47
|
+
ref,
|
|
48
|
+
allowDangerousKeys,
|
|
49
|
+
isTTY,
|
|
50
|
+
operation: "install",
|
|
51
|
+
rollback: async () => {
|
|
52
|
+
try {
|
|
53
|
+
await akmRemove({ target: rollbackTarget });
|
|
54
|
+
return undefined;
|
|
192
55
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const rollbackWarning = await rollback();
|
|
200
|
-
console.error(JSON.stringify({
|
|
201
|
-
ok: false,
|
|
202
|
-
error: "Install aborted: stash contains dangerous env keys. Remove the keys or re-run with --allow-insecure to bypass.",
|
|
203
|
-
code: "DANGEROUS_ENV_KEY",
|
|
204
|
-
...(rollbackWarning ? { rollbackWarning } : {}),
|
|
205
|
-
}, null, 2));
|
|
206
|
-
return { blocked: true, exitCode: 1 };
|
|
207
|
-
}
|
|
208
|
-
// Operator confirmed at the prompt — allow the install to proceed.
|
|
209
|
-
return { blocked: false };
|
|
210
|
-
}
|
|
211
|
-
// Non-interactive path without bypass flag: fail hard.
|
|
212
|
-
const rollbackWarning = await rollback();
|
|
213
|
-
const keyList = allFindings.map((f) => ` - ${f.keyName} (${f.envRef})`).join("\n");
|
|
214
|
-
console.error(JSON.stringify({
|
|
215
|
-
ok: false,
|
|
216
|
-
error: `Install blocked: stash "${ref}" contains dangerous env keys that can hijack process execution via \`akm env run\`:\n${keyList}\nRe-run with --allow-insecure to bypass this check after reviewing the env file.`,
|
|
217
|
-
code: "DANGEROUS_ENV_KEY",
|
|
218
|
-
...(rollbackWarning ? { rollbackWarning } : {}),
|
|
219
|
-
}, null, 2));
|
|
220
|
-
return { blocked: true, exitCode: 1 };
|
|
56
|
+
catch {
|
|
57
|
+
return (`Rollback failed — stash may still be installed at ${installedStashRoot}. ` +
|
|
58
|
+
`Remove it manually with: akm bundle remove ${rollbackTarget}`);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
221
62
|
}
|
|
222
63
|
// ── Command definition ────────────────────────────────────────────────────────
|
|
223
64
|
export const addCommand = defineJsonCommand({
|
|
@@ -118,11 +118,16 @@ const removeCommand = defineJsonCommand({
|
|
|
118
118
|
},
|
|
119
119
|
});
|
|
120
120
|
const updateCommand = defineJsonCommand({
|
|
121
|
-
meta: { name: "update", description: "
|
|
121
|
+
meta: { name: "update", description: "Refresh one or all configured bundles and reconcile their index" },
|
|
122
122
|
args: {
|
|
123
123
|
target: { type: "positional", description: "Bundle to update (id or ref)", required: false },
|
|
124
|
-
all: { type: "boolean", description: "Update all
|
|
124
|
+
all: { type: "boolean", description: "Update all configured bundles and report each outcome", default: false },
|
|
125
125
|
force: { type: "boolean", description: "Force fresh download even if version is unchanged", default: false },
|
|
126
|
+
"allow-insecure": {
|
|
127
|
+
type: "boolean",
|
|
128
|
+
description: "Allow an update containing dangerous env keys (e.g. LD_PRELOAD, PATH). Use only after explicitly reviewing the staged bundle.",
|
|
129
|
+
default: false,
|
|
130
|
+
},
|
|
126
131
|
// F1/R-058: gates ONLY the rare branch where the resolved content
|
|
127
132
|
// directory moves and the previous `localRoot` is deleted — a normal
|
|
128
133
|
// refresh (the overwhelming majority of updates) deletes nothing and
|
|
@@ -135,13 +140,20 @@ const updateCommand = defineJsonCommand({
|
|
|
135
140
|
},
|
|
136
141
|
},
|
|
137
142
|
async run({ args }) {
|
|
138
|
-
const result = await akmUpdate({
|
|
143
|
+
const result = await akmUpdate({
|
|
144
|
+
target: args.target,
|
|
145
|
+
all: args.all,
|
|
146
|
+
force: args.force,
|
|
147
|
+
yes: args.yes,
|
|
148
|
+
allowInsecure: args["allow-insecure"],
|
|
149
|
+
});
|
|
139
150
|
appendEvent({
|
|
140
151
|
eventType: "update",
|
|
141
152
|
metadata: {
|
|
142
153
|
target: args.target ?? null,
|
|
143
154
|
all: args.all === true,
|
|
144
155
|
force: args.force === true,
|
|
156
|
+
allowInsecure: args["allow-insecure"] === true,
|
|
145
157
|
processed: Array.isArray(result.processed)
|
|
146
158
|
? result.processed.length
|
|
147
159
|
: 0,
|
|
@@ -0,0 +1,135 @@
|
|
|
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 * as p from "../../cli/clack.js";
|
|
7
|
+
import { decideDangerousKeyInstall } from "../../core/activation-policy.js";
|
|
8
|
+
import { ConfigError } from "../../core/errors.js";
|
|
9
|
+
import { warn } from "../../core/warn.js";
|
|
10
|
+
import { sanitizeString } from "../../sources/providers/provider-utils.js";
|
|
11
|
+
import { scanEnvKeyNames } from "../env/env.js";
|
|
12
|
+
import { isDangerousEnvKey } from "../lint/env-key-rules.js";
|
|
13
|
+
let scannerOverride;
|
|
14
|
+
/** TEST-ONLY. Inject a scanner fault at the audit boundary; undefined restores. */
|
|
15
|
+
export function _setDangerousKeyScannerForTests(scanner) {
|
|
16
|
+
scannerOverride = scanner;
|
|
17
|
+
}
|
|
18
|
+
function collectEnvFilePathsRecursive(rootDir) {
|
|
19
|
+
const results = [];
|
|
20
|
+
const walk = (dir) => {
|
|
21
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
const full = path.join(dir, entry.name);
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
walk(full);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (!entry.isFile())
|
|
29
|
+
continue;
|
|
30
|
+
if (entry.name !== ".env" && !entry.name.endsWith(".env"))
|
|
31
|
+
continue;
|
|
32
|
+
results.push(full);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
walk(rootDir);
|
|
36
|
+
return results;
|
|
37
|
+
}
|
|
38
|
+
function collectDangerousKeyFindings(installedStashRoot, checkEnvForDangerousKeys) {
|
|
39
|
+
const allFindings = [];
|
|
40
|
+
if (!fs.statSync(installedStashRoot).isDirectory()) {
|
|
41
|
+
throw new Error(`Expected ${installedStashRoot} to be a directory.`);
|
|
42
|
+
}
|
|
43
|
+
const dir = path.join(installedStashRoot, "env");
|
|
44
|
+
try {
|
|
45
|
+
if (!fs.statSync(dir).isDirectory()) {
|
|
46
|
+
throw new Error(`Expected ${dir} to be a directory.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error.code === "ENOENT")
|
|
51
|
+
return allFindings;
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
for (const envPath of collectEnvFilePathsRecursive(dir)) {
|
|
55
|
+
const relToEnvDir = path.relative(dir, envPath);
|
|
56
|
+
const segments = relToEnvDir.split(path.sep);
|
|
57
|
+
const lastSegment = segments[segments.length - 1] ?? "";
|
|
58
|
+
const lastSegmentBase = lastSegment.endsWith(".env") ? lastSegment.slice(0, -".env".length) : lastSegment;
|
|
59
|
+
const refSegments = [...segments.slice(0, -1), lastSegmentBase === "" ? "default" : lastSegmentBase];
|
|
60
|
+
const envRef = `env/${refSegments.join("/")}`;
|
|
61
|
+
const relPath = path.join("env", relToEnvDir);
|
|
62
|
+
for (const finding of checkEnvForDangerousKeys(envPath, relPath, envRef)) {
|
|
63
|
+
const keyMatch = finding.detail.match(/Env key `([^`]+)`/);
|
|
64
|
+
allFindings.push({ envRef, keyName: keyMatch?.[1] ?? finding.file, relPath });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return allFindings;
|
|
68
|
+
}
|
|
69
|
+
/** Scan only; callers that already obtained approval use this for a final materialization fence. */
|
|
70
|
+
export function scanStashForDangerousKeys(installedStashRoot) {
|
|
71
|
+
const scanner = scannerOverride ??
|
|
72
|
+
((envPath, relPath) => scanEnvKeyNames(fs.readFileSync(envPath, "utf8"))
|
|
73
|
+
.filter(isDangerousEnvKey)
|
|
74
|
+
.map((key) => ({ file: relPath, detail: `Env key \`${key}\`` })));
|
|
75
|
+
return collectDangerousKeyFindings(installedStashRoot, scanner);
|
|
76
|
+
}
|
|
77
|
+
export async function auditStashForDangerousKeys(opts) {
|
|
78
|
+
let allFindings;
|
|
79
|
+
try {
|
|
80
|
+
allFindings = scanStashForDangerousKeys(opts.stashRoot);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const rollbackWarning = await opts.rollback?.();
|
|
84
|
+
throw new ConfigError(`Dangerous environment-key audit failed for "${opts.ref}"; refusing to ${opts.operation} unverified content. ${error instanceof Error ? error.message : String(error)}${rollbackWarning ? ` ${rollbackWarning}` : ""}`, "DANGEROUS_ENV_AUDIT_FAILED");
|
|
85
|
+
}
|
|
86
|
+
const stance = decideDangerousKeyInstall({
|
|
87
|
+
findingsPresent: allFindings.length > 0,
|
|
88
|
+
allowInsecure: opts.allowDangerousKeys,
|
|
89
|
+
});
|
|
90
|
+
if (stance === "allow")
|
|
91
|
+
return { blocked: false, findings: allFindings };
|
|
92
|
+
if (stance === "warn-allow") {
|
|
93
|
+
for (const finding of allFindings) {
|
|
94
|
+
warn(`[dangerous-env-key] ${finding.relPath}: key \`${finding.keyName}\` in ${finding.envRef} can hijack process execution via \`akm env run\`. Proceeding because --allow-insecure was set.`);
|
|
95
|
+
}
|
|
96
|
+
return { blocked: false, findings: allFindings };
|
|
97
|
+
}
|
|
98
|
+
const operationTitle = opts.operation === "install" ? "Install" : "Update";
|
|
99
|
+
const operationPast = opts.operation === "install" ? "installed" : "updated";
|
|
100
|
+
let error;
|
|
101
|
+
if (opts.isTTY) {
|
|
102
|
+
const groupedByEnv = new Map();
|
|
103
|
+
for (const finding of allFindings) {
|
|
104
|
+
const existing = groupedByEnv.get(finding.envRef) ?? [];
|
|
105
|
+
existing.push(finding.keyName);
|
|
106
|
+
groupedByEnv.set(finding.envRef, existing);
|
|
107
|
+
}
|
|
108
|
+
for (const [envRef, keys] of groupedByEnv) {
|
|
109
|
+
warn(`[warn] Env "${sanitizeString(envRef)}" in stash "${sanitizeString(opts.ref)}" contains potentially dangerous keys:`);
|
|
110
|
+
for (const key of keys)
|
|
111
|
+
warn(` - ${sanitizeString(key)}: can hijack process execution via \`akm env run\``);
|
|
112
|
+
}
|
|
113
|
+
const confirmed = await p.confirm({ message: `${operationTitle} anyway?`, initialValue: false });
|
|
114
|
+
if (!p.isCancel(confirmed) && confirmed === true)
|
|
115
|
+
return { blocked: false, findings: allFindings };
|
|
116
|
+
error = `${operationTitle} aborted: stash contains dangerous env keys. Remove the keys or re-run with --allow-insecure to bypass.`;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const keyList = allFindings.map((finding) => ` - ${finding.keyName} (${finding.envRef})`).join("\n");
|
|
120
|
+
error = `${operationTitle} blocked: stash "${opts.ref}" contains dangerous env keys that can hijack process execution via \`akm env run\`:\n${keyList}\nRe-run with --allow-insecure to bypass this check after reviewing the env file.`;
|
|
121
|
+
}
|
|
122
|
+
const rollbackWarning = await opts.rollback?.();
|
|
123
|
+
if (opts.renderBlockedError !== false) {
|
|
124
|
+
console.error(JSON.stringify({
|
|
125
|
+
ok: false,
|
|
126
|
+
error,
|
|
127
|
+
code: "DANGEROUS_ENV_KEY",
|
|
128
|
+
...(rollbackWarning ? { rollbackWarning } : {}),
|
|
129
|
+
}, null, 2));
|
|
130
|
+
}
|
|
131
|
+
if (rollbackWarning) {
|
|
132
|
+
warn(`The blocked ${opts.operation} could not be fully rolled back after content was ${operationPast}: ${rollbackWarning}`);
|
|
133
|
+
}
|
|
134
|
+
return { blocked: true, exitCode: 1, code: "DANGEROUS_ENV_KEY", error, findings: allFindings };
|
|
135
|
+
}
|
|
@@ -6,6 +6,7 @@ import { resolveStashDir } from "../../core/common.js";
|
|
|
6
6
|
import { getSources, loadConfig } from "../../core/config/config.js";
|
|
7
7
|
import { classifyPathAccess, describeInaccessiblePath } from "../../core/path-access.js";
|
|
8
8
|
import { getDbPath } from "../../core/paths.js";
|
|
9
|
+
import { formatRegistryUrl } from "../../core/registry-url.js";
|
|
9
10
|
import { error } from "../../core/warn.js";
|
|
10
11
|
import { getEffectiveSemanticStatus, readSemanticStatus } from "../../indexer/search/semantic-status.js";
|
|
11
12
|
import { closeDatabase, openExistingDatabase } from "../../storage/repositories/index-connection.js";
|
|
@@ -37,7 +38,7 @@ export function assembleInfo(options) {
|
|
|
37
38
|
}
|
|
38
39
|
// Registries (strip sensitive fields like apiKey from options)
|
|
39
40
|
const registries = (config.registries ?? []).map((r) => ({
|
|
40
|
-
url: r.url,
|
|
41
|
+
url: formatRegistryUrl(r.url),
|
|
41
42
|
...(r.name ? { name: r.name } : {}),
|
|
42
43
|
...(r.provider ? { provider: r.provider } : {}),
|
|
43
44
|
...(r.enabled !== undefined ? { enabled: r.enabled } : {}),
|