akm-cli 0.10.261012-beta → 0.10.261013-beta
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 +216 -0
- package/SECURITY.md +2 -2
- package/STABILITY.md +2 -2
- package/dist/assets/hints/cli-hints-full.md +6 -7
- package/dist/assets/hints/cli-hints-short.md +3 -4
- package/dist/commands/command/execution-source-loader.js +10 -14
- package/dist/commands/env/env-binding.js +47 -58
- package/dist/commands/env/env-cli.js +20 -22
- package/dist/commands/env/env.js +10 -58
- package/dist/commands/env/secret-cli.js +9 -27
- package/dist/commands/env/secret.js +3 -10
- package/dist/commands/health/scheduler-binary.js +1 -1
- package/dist/commands/improve/distill.js +0 -2
- package/dist/commands/improve/eligibility.js +0 -2
- package/dist/commands/lint/env-key-rules.js +7 -6
- package/dist/commands/lint/index.js +1 -1
- package/dist/commands/proposal/validators/proposal-validators.js +1 -4
- package/dist/commands/read/show.js +4 -25
- package/dist/commands/sources/add-cli.js +8 -63
- package/dist/commands/sources/bundle-cli.js +8 -9
- package/dist/commands/sources/installed-stashes.js +117 -61
- package/dist/commands/sources/schema-repair.js +0 -2
- package/dist/commands/sources/source-clone.js +31 -7
- package/dist/commands/tasks/explain.js +1 -1
- package/dist/commands/tasks/tasks.js +38 -56
- package/dist/commands/ui/redact.js +4 -4
- package/dist/commands/ui/workspace-actions.js +1 -1
- package/dist/commands/workflow/plan.js +47 -8
- package/dist/commands/workflow-cli.js +37 -41
- package/dist/core/adapter/adapters/akm-adapter.js +3 -3
- package/dist/core/adapter/adapters/akm-lint.js +3 -4
- package/dist/core/adapter/adapters/akm-metadata.js +2 -2
- package/dist/core/adapter/adapters/akm-task-adapter.js +5 -6
- package/dist/core/adapter/adapters/dotenv-adapter.js +4 -3
- package/dist/core/asset/resolve-ref.js +5 -0
- package/dist/core/bundle-rename.js +12 -4
- package/dist/core/common.js +4 -52
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +55 -18
- package/dist/core/config/schema/primitives.js +12 -6
- package/dist/core/config/schema/sources-bundles.js +2 -2
- package/dist/core/env-secret-ref.js +84 -73
- package/dist/core/errors.js +1 -1
- package/dist/core/paths.js +1 -6
- package/dist/core/redaction.js +16 -3
- package/dist/core/ref-token.js +82 -0
- package/dist/core/secret-value.js +14 -0
- package/dist/core/write-source.js +20 -4
- package/dist/indexer/indexer.js +27 -36
- package/dist/indexer/installations.js +1 -8
- package/dist/indexer/lookup/adapter-concept-owner.js +40 -14
- package/dist/indexer/materialize-embeddings.js +1 -1
- package/dist/indexer/scan/drain-dir.js +3 -3
- package/dist/indexer/search/search-source.js +4 -2
- package/dist/indexer/walk/path-resolver.js +13 -68
- package/dist/indexer/walk/walker.js +18 -22
- package/dist/integrations/agent/engine-resolution.js +9 -7
- package/dist/integrations/agent/runner-dispatch.js +4 -5
- package/dist/integrations/agent/runner.js +2 -0
- package/dist/llm/client.js +1 -1
- package/dist/llm/embedders/remote.js +4 -3
- package/dist/output/renderers.js +3 -4
- package/dist/output/shapes/helpers.js +0 -2
- package/dist/output/text/command-format.js +12 -2
- package/dist/output/text/show-directives.js +16 -28
- package/dist/output/text/workflow-format.js +37 -7
- package/dist/scripts/akm-migrate-node.js +45865 -44692
- package/dist/scripts/akm-migrate.js +45804 -44630
- package/dist/setup/steps/tasks.js +1 -2
- package/dist/sources/providers/provider-utils.js +12 -0
- package/dist/sources/resolve.js +12 -16
- package/dist/sources/snapshot-fetchers/secret-seam.js +2 -4
- package/dist/sources/snapshot-fetchers/x.js +1 -1
- package/dist/storage/repositories/proposals-repository.js +2 -2
- package/dist/storage/repositories/task-history-repository.js +2 -2
- package/dist/storage/repositories/workflow-runs-repository.js +38 -116
- package/dist/tasks/embedded.js +1 -1
- package/dist/tasks/prepare/prepare-support.js +3 -2
- package/dist/tasks/prepare/prepare.js +3 -4
- package/dist/tasks/prepare/script-capture.js +8 -22
- package/dist/tasks/run/load-task.js +46 -14
- package/dist/tasks/run/run-native-task.js +0 -8
- package/dist/tasks/run/run-workflow-task.js +2 -2
- package/dist/tasks/scheduler-sync.js +0 -1
- package/dist/tasks/source/bounded-document.js +0 -41
- package/dist/tasks/source/parse-task-source.js +0 -1
- package/dist/tasks/source/task-source-v4.js +1 -4
- package/dist/workflows/compile.js +1 -15
- package/dist/workflows/exec/child-workflow.js +1 -2
- package/dist/workflows/exec/dispatch-redaction.js +24 -6
- package/dist/workflows/exec/environment.js +11 -74
- package/dist/workflows/exec/exec-unit.js +31 -23
- package/dist/workflows/exec/frozen-judge.js +17 -3
- package/dist/workflows/exec/native-executor.js +70 -26
- package/dist/workflows/exec/run-workflow.js +31 -18
- package/dist/workflows/exec/step-work.js +4 -3
- package/dist/workflows/exec/unit-dispatch.js +1 -8
- package/dist/workflows/freeze/environment.js +14 -119
- package/dist/workflows/freeze/freeze.js +0 -1
- package/dist/workflows/freeze/step-values.js +1 -2
- package/dist/workflows/freeze/targets/child-workflow.js +3 -5
- package/dist/workflows/freeze/targets/command.js +6 -9
- package/dist/workflows/freeze/targets/shell.js +0 -3
- package/dist/workflows/parser.js +6 -25
- package/dist/workflows/plan.js +6 -2
- package/dist/workflows/renderer.js +77 -4
- package/dist/workflows/runtime/run-plan.js +37 -23
- package/dist/workflows/runtime/runs.js +41 -84
- package/dist/workflows/runtime/workflow-asset-loader.js +55 -86
- package/dist/workflows/source-files.js +7 -19
- package/docs/architecture/0.10-process-rules.md +6 -6
- package/docs/architecture/architecture.md +6 -6
- package/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md +12 -18
- package/docs/architecture/decisions/0003-child-env-allowlist-and-provenance.md +6 -8
- package/docs/architecture/internals/storage-locations.md +4 -4
- package/docs/architecture/persisted-data-compat.md +3 -2
- package/docs/architecture/specs/0.9.0-public-api-issue-backlog.md +1 -1
- package/docs/architecture/specs/akm-format-neutral-bundle-workspace-spec.md +3 -3
- package/docs/architecture/testing/manual-testing-checklist.md +46 -78
- package/docs/architecture/testing/task-workflow-manual-tests.md +22 -40
- package/docs/architecture/testing/testing-workflow.md +9 -11
- package/docs/architecture/workflow-engine.md +41 -27
- package/docs/guides/author-bundles.md +14 -17
- package/docs/guides/author-workflows.md +43 -9
- package/docs/guides/bundles.md +16 -11
- package/docs/guides/claude-code-vs-akm-workflows.md +1 -1
- package/docs/guides/run-workflows.md +25 -13
- package/docs/guides/scheduling.md +1 -1
- package/docs/maintainers/README.md +2 -3
- package/docs/maintainers/release-checklist.md +73 -126
- package/docs/migration/v0.9-to-v0.10.md +5 -1
- package/docs/reference/cli.md +124 -112
- package/docs/reference/configuration.md +16 -9
- package/docs/reference/data-and-telemetry.md +1 -1
- package/docs/reference/env-and-secrets.md +66 -15
- package/docs/reference/registry.md +6 -11
- package/docs/reference/tasks.md +11 -3
- package/docs/reference/ui-api.md +3 -3
- package/docs/reference/workflow-schema.md +109 -36
- package/package.json +1 -1
- package/schemas/akm-task.json +2 -4
- package/dist/commands/sources/dangerous-env-audit.js +0 -134
- package/dist/core/activation-policy.js +0 -67
- package/dist/execution/directory-identity.js +0 -78
- package/dist/workflows/authoring/scope-key.js +0 -79
- package/dist/workflows/source-semantics.js +0 -110
|
@@ -19,6 +19,16 @@ command; use `env` or `secret`.
|
|
|
19
19
|
| Discoverable | key *names* (not values) | name only (the whole file is the value) |
|
|
20
20
|
| Injects | many vars at once (`env run`) | one var (`secret run <ref> <VAR>`) |
|
|
21
21
|
|
|
22
|
+
## Addressing: the akm ref
|
|
23
|
+
|
|
24
|
+
`env` and `secret` assets are addressed like every other asset, by their akm
|
|
25
|
+
ref `[bundle//]conceptId`: `env/prod`, `secrets/gitea_token`,
|
|
26
|
+
`team//secrets/discord_bot_token`. There is no bare-name form. `akm secret
|
|
27
|
+
run gitea_token ...` and `akm env run prod -- ...` are usage errors that name
|
|
28
|
+
the full ref (`secrets/<name>`, `env/<name>`). An unqualified ref resolves the
|
|
29
|
+
way every unqualified ref does: the default bundle first, then installation
|
|
30
|
+
priority.
|
|
31
|
+
|
|
22
32
|
## Core security guarantee
|
|
23
33
|
|
|
24
34
|
Values never appear in akm's structured output. `env list` and `akm show
|
|
@@ -31,8 +41,8 @@ the child process — never through akm's own stdout, the index, or `akm
|
|
|
31
41
|
show`.
|
|
32
42
|
|
|
33
43
|
```sh
|
|
34
|
-
akm env create prod
|
|
35
|
-
akm env create prod --from-file ./.env
|
|
44
|
+
akm env create env/prod # create an empty .env group
|
|
45
|
+
akm env create env/prod --from-file ./.env # or ingest an existing .env
|
|
36
46
|
|
|
37
47
|
# akm does not manage individual keys — edit the whole file with your own editor:
|
|
38
48
|
$EDITOR "$(akm env path env/prod --quiet)"
|
|
@@ -58,17 +68,51 @@ script (re-serialised single-quoted, so shell substitutions in the source
|
|
|
58
68
|
`.env` become literal strings) — it requires `--out` and never prints values
|
|
59
69
|
to stdout.
|
|
60
70
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
## The `${ref}` token
|
|
72
|
+
|
|
73
|
+
Wherever akm substitutes a reference into a string it hands to a process or a
|
|
74
|
+
provider, the token is `${[bundle//]conceptId}`:
|
|
75
|
+
|
|
76
|
+
- an env value: `DISCORD_BOT_TOKEN=${team//secrets/discord_bot_token}`;
|
|
77
|
+
- a config credential (`engines.<name>.apiKey`, `embedding.apiKey`, a git
|
|
78
|
+
bundle's `credential`): `"${secrets/openai}"`.
|
|
79
|
+
|
|
80
|
+
Where the token may appear decides what it may name:
|
|
81
|
+
|
|
82
|
+
- **In an env value**, any asset ref substitutes. A `secrets/...` ref stands for
|
|
83
|
+
the secret's value; any other asset ref (for example
|
|
84
|
+
`${scripts/lab/fetch.sh}`) stands for the asset's absolute path.
|
|
85
|
+
- **In a config credential**, only a `secrets/...` ref is accepted. A `${...}`
|
|
86
|
+
naming any other kind of asset is rejected when the config loads.
|
|
87
|
+
|
|
88
|
+
The ref is resolved with the one ref walk, so it may name any bundle and any
|
|
89
|
+
asset its owner holds. A `${...}` body is a ref when it leads with a native type
|
|
90
|
+
directory, qualified or not (`${secrets/x}`, `${scripts/run.sh}`,
|
|
91
|
+
`${team//secrets/x}`), or when it is bundle-qualified and that bundle is
|
|
92
|
+
installed (`${docs//tables/customers}`, whatever adapter owns it). `akm env
|
|
93
|
+
run` and a workflow unit's `env:` binding replace the tokens in an env file's
|
|
94
|
+
values; `env export` writes them verbatim. A `${...}` body that is not an akm
|
|
95
|
+
ref (`${HOME}`, `${PATH//:/ }`, and shell substitution such as `${v//-/_}`
|
|
96
|
+
unless a bundle named `v` is installed) is plain text: it is left untouched in an env
|
|
97
|
+
value, and in a config credential a `${NAME}` that is a valid variable name is
|
|
98
|
+
expanded from the process environment. A ref no bundle owns is an error and
|
|
99
|
+
nothing is injected. Each read records an `env_access` event. See
|
|
70
100
|
[Workflow Schema: Binding a secret to a unit](workflow-schema.md#binding-a-secret-to-a-unit).
|
|
71
101
|
|
|
102
|
+
A secret's value is its file content with surrounding whitespace trimmed. That
|
|
103
|
+
one read is used by `akm secret run`, env tokens, config credentials and the
|
|
104
|
+
website fetchers, so a hand-written file's trailing newline never lands in an
|
|
105
|
+
HTTP header.
|
|
106
|
+
|
|
107
|
+
`${secret:NAME}` was the old env-value spelling, meaning the secret `NAME` in the
|
|
108
|
+
env file's own bundle. akm still reads it that way, as `${<bundle>//secrets/NAME}`
|
|
109
|
+
with the bundle that owns the env file, and warns once naming the env ref. The
|
|
110
|
+
env file is bundle content and is not rewritten; write the full ref.
|
|
111
|
+
|
|
112
|
+
`secret://name` was the config spelling of the same thing. It is retired:
|
|
113
|
+
config still reads it as `${secrets/name}` and names the key once, and `akm
|
|
114
|
+
migrate apply` rewrites the file.
|
|
115
|
+
|
|
72
116
|
## File modes and storage
|
|
73
117
|
|
|
74
118
|
`.env` files are stored at mode 0600 under `env/` in the target bundle;
|
|
@@ -80,6 +124,12 @@ secret can carry a sibling `.sensitive` marker that excludes it from `list`
|
|
|
80
124
|
output and from the search index entirely; the value remains usable via
|
|
81
125
|
`run`.
|
|
82
126
|
|
|
127
|
+
An env file or secret may be a symlink to a file anywhere else, such as a
|
|
128
|
+
container's `/run/secrets/<name>`: `akm env`, `akm secret`, `${secrets/name}`
|
|
129
|
+
substitution and a workflow `env:` binding follow it. A name still cannot climb
|
|
130
|
+
out of `env/` or `secrets/` with `..` or an absolute path. Writes never follow
|
|
131
|
+
a symlink out of the bundle.
|
|
132
|
+
|
|
83
133
|
## Write-target resolution
|
|
84
134
|
|
|
85
135
|
Env/secret **mutations** (`env create`, `env remove`, `secret set`) choose
|
|
@@ -108,10 +158,11 @@ subprocess the child spawns.
|
|
|
108
158
|
- Avoid running commands that print the environment (`env`, `printenv`,
|
|
109
159
|
shell tracing, verbose diagnostics) in agent contexts unless you
|
|
110
160
|
explicitly intend to expose the child environment.
|
|
111
|
-
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
161
|
+
- Installing a bundle is the trust decision: `env run`, `secret run` and
|
|
162
|
+
workflow `env:` bindings inject what the bundle's files define, including
|
|
163
|
+
keys such as `PATH` or `LD_PRELOAD`, from any bundle, with no warning, block
|
|
164
|
+
or flag. `akm lint` still reports such keys (`dangerous-env-key`,
|
|
165
|
+
non-blocking) so an author can review their own files.
|
|
115
166
|
|
|
116
167
|
## Stability
|
|
117
168
|
|
|
@@ -215,17 +215,12 @@ akm bundle add file:///absolute/path/to/bundle
|
|
|
215
215
|
3. **Download and extract** -- The tarball is downloaded (or repo cloned) to a
|
|
216
216
|
cache directory under `~/.cache/akm/registry/` and extracted securely
|
|
217
217
|
(path traversal is rejected).
|
|
218
|
-
4. **
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
`akm bundle add` **blocks the install** when a dangerous key is present unless
|
|
225
|
-
`--allow-dangerous-env-keys` is set (or you confirm at an interactive prompt).
|
|
226
|
-
This is a **key-name audit only** (plus the path-traversal rejection in step
|
|
227
|
-
3) -- akm does **not** scan source files, prompts, metadata, or install
|
|
228
|
-
scripts for prompt-injection phrases, shell pipes, or lifecycle hooks.
|
|
218
|
+
4. **No content audit** -- Installing a bundle is the trust decision. akm does
|
|
219
|
+
**not** scan the extracted bundle's source files, prompts, metadata, env
|
|
220
|
+
files or install scripts (beyond the path-traversal rejection in step 3), and
|
|
221
|
+
`akm bundle add` never blocks or prompts over the keys an env file sets.
|
|
222
|
+
`akm lint` reports env keys that can hijack a process (`dangerous-env-key`)
|
|
223
|
+
for authors who want to review their own bundle.
|
|
229
224
|
5. **Bundle root detection** -- The extracted contents are scanned for asset
|
|
230
225
|
type directories (`scripts/`, `skills/`, etc.) or a `.stash/` marker. If the
|
|
231
226
|
bundle nests its bundle under an `opencode/` subdirectory, that is detected
|
package/docs/reference/tasks.md
CHANGED
|
@@ -116,9 +116,17 @@ Script refs use this closed table; any other extension fails before dispatch:
|
|
|
116
116
|
`run` is one non-empty shell string. It may specify `shell` from the closed host
|
|
117
117
|
shell table `bash`, `sh`, `zsh`, `pwsh`, `powershell`, or `cmd`. Shell expansion
|
|
118
118
|
is runtime behavior for an explicitly authored task `run`; AKM does not infer a
|
|
119
|
-
shell from `uses`. `working-directory`
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
shell from `uses`. `working-directory` is a non-empty string, relative to the
|
|
120
|
+
task's bundle root or absolute, like a workflow's `exec.cwd`. akm does not
|
|
121
|
+
keep it inside the bundle or check it when the task source is parsed, so a
|
|
122
|
+
symlink that resolves outside the bundle works. The task runs in
|
|
123
|
+
`<bundle root>/<working-directory>` (or the absolute path as written): akm does
|
|
124
|
+
not capture the directory's identity or re-check it when the task runs. A
|
|
125
|
+
`uses: scripts/<file>` target may itself be a symlink to a file outside the
|
|
126
|
+
bundle; the task freezes that file's bytes, not its location. The interpreter
|
|
127
|
+
comes from the name the script has in the bundle, not from the file the link
|
|
128
|
+
points to, so `scripts/deploy.sh` linked to `real-script.txt` runs under the
|
|
129
|
+
shell.
|
|
122
130
|
|
|
123
131
|
Every field that used to live under v3's `akm:` options bag is a top-level
|
|
124
132
|
key in task source v4: `agent`, `engine`, `model`, `inference`, `tools`,
|
package/docs/reference/ui-api.md
CHANGED
|
@@ -32,7 +32,7 @@ release, and nothing outside the page should script against them. Use the CLI's
|
|
|
32
32
|
`ORIGIN_NOT_ALLOWED`, `METHOD_NOT_ALLOWED`, `UNSUPPORTED_MEDIA_TYPE`, `BODY_TOO_LARGE`, `INVALID_JSON_ARGUMENT`,
|
|
33
33
|
`NOT_FOUND`, `INTERNAL_ERROR`.
|
|
34
34
|
- **Redaction.** Config values under secret-like keys (`apiKey`, `token`, `password`, `secret`, `credential`,
|
|
35
|
-
`authorization`, anything under `env` or `secrets`) are `"[redacted]"`; symbolic references (`$VAR`,
|
|
35
|
+
`authorization`, anything under `env` or `secrets`) are `"[redacted]"`; symbolic references (`$VAR`, `${secrets/name}`)
|
|
36
36
|
are kept. Credentials inside any URL in any response (userinfo, `token=`, `api_key=`, `sig=`, ...) are masked. Env and
|
|
37
37
|
secret assets are never read; see `/api/asset`.
|
|
38
38
|
- **Timestamps** are ISO 8601 UTC unless noted. Counts that cannot be computed are `null`, never `NaN`.
|
|
@@ -616,7 +616,7 @@ general config editor or command-execution endpoint.
|
|
|
616
616
|
| `POST` route | Body and existing operation |
|
|
617
617
|
| --- | --- |
|
|
618
618
|
| `/api/feedback` | `{ref, signal: "positive"\|"negative", reason?, tags?: string[]}` → `akm feedback`. The ref is passed to `akm feedback` as given, which resolves it as the CLI does; tags follow existing `key:value` validation. Negative feedback requires a reason when `feedback.requireReason` is enabled. Submissions are recorded with source `user` unless the server runs with an `AKM_EVENT_SOURCE` already set (for example under an improve or task run), which wins as it does for every akm child; they retain existing ranking/utility/event effects. Feedback does not save config, so it works under a config that still holds a literal API key. No fix, supersession or model flags are exposed. |
|
|
619
|
-
| `/api/bundles/add` | `{source, name?, writable?}` → `akm bundle add`, including source validation
|
|
619
|
+
| `/api/bundles/add` | `{source, name?, writable?}` → `akm bundle add`, including source validation. Existing provider restrictions apply; no insecure-transport bypass is exposed. |
|
|
620
620
|
| `/api/bundles/update` | `{bundle}` → `akm bundle update -- <bundle>`. Fetches/reindexes only on explicit submission. A rare update requiring destructive confirmation remains refused; the UI does not silently pass `--yes`. |
|
|
621
621
|
| `/api/bundles/remove` | `{bundle, confirm: true}` → `akm bundle remove --yes -- <bundle>`. The UI requires an inline confirmation before this call. Managed installs may be deleted; local filesystem source files remain on disk. |
|
|
622
622
|
| `/api/bundles/settings` | `{bundle?, enabled?, writable?, componentWritability?: {existingComponent: boolean}, defaultBundle?, defaultWriteTarget?}` → existing schema-walking config setters inside `mutateConfig`. Only changed controls are submitted. Defaults take existing names or `""` to unset a local value; inherited values cannot be unset locally. Existing validation rejects invalid defaults, disabled targets and writable npm/website sources. |
|
|
@@ -624,7 +624,7 @@ general config editor or command-execution endpoint.
|
|
|
624
624
|
| `/api/asset/save` | `{ref, revision, content}` → the existing shared Markdown writer with `force` and the expected raw-content hash. Only indexed writable AKM knowledge/memory documents accepted by `/api/asset/source` can be edited. The writer takes the asset-mutation lease and compares the live source with `revision` inside it, immediately before writing. While another writer holds the lease the save fails fast with `409 ASSET_MUTATION_LEASE_HELD` instead of waiting. A stale revision returns `409 ASSET_CONTENT_CHANGED` without writing; a source deleted after it was loaded is refused (`404 ASSET_NOT_FOUND`) rather than recreated. The browser keeps the draft after a rejected save. An identical save returns `unchanged: true` without rewriting, reindexing or recording an event. Changed saves retain normal writer validation, indexing and Git effects. |
|
|
625
625
|
|
|
626
626
|
Configuration-affecting actions do not round-trip the redacted API configuration. Existing mutation helpers retain
|
|
627
|
-
unknown/unrelated fields, local overrides and supported `$VAR
|
|
627
|
+
unknown/unrelated fields, local overrides and supported `$VAR`/`${ref}` credential references. If the existing
|
|
628
628
|
config sanitizer would remove unrelated legacy literal credentials, a bundle add or remove, or a settings change that
|
|
629
629
|
would write config, is refused with `400 INVALID_FLAG_VALUE` and the user is asked to correct those settings through
|
|
630
630
|
the CLI first. A settings request that changes nothing writes nothing and is not refused. It never writes
|
|
@@ -515,7 +515,7 @@ steps:
|
|
|
515
515
|
unit:
|
|
516
516
|
exec:
|
|
517
517
|
command: ["bun", "run", "test:unit"]
|
|
518
|
-
cwd: packages/core # optional,
|
|
518
|
+
cwd: packages/core # optional: relative to the workflow's bundle root, or absolute
|
|
519
519
|
timeout: "10m"
|
|
520
520
|
retry: { max: 1, on: [timeout] }
|
|
521
521
|
```
|
|
@@ -554,11 +554,66 @@ Bounds: 1–64 argv entries, each a non-empty string of at most 4096 bytes.
|
|
|
554
554
|
|
|
555
555
|
### `cwd`
|
|
556
556
|
|
|
557
|
-
Optional
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
557
|
+
Optional: a non-empty string, **relative or absolute**, and free to carry
|
|
558
|
+
`${ref}` tokens (see [`${ref}` in argv and `cwd`](#ref-in-argv-and-cwd)). It is
|
|
559
|
+
the only static check; there is no containment.
|
|
560
|
+
|
|
561
|
+
An exec unit's working directory is the **root of the bundle that owns the
|
|
562
|
+
workflow**. akm finds that bundle again for each step, by the run's workflow
|
|
563
|
+
ref, so a `bundle update` that moves the bundle to a new revision directory
|
|
564
|
+
between two steps of a running workflow is harmless: the next step runs from
|
|
565
|
+
the new directory. `cwd` is joined onto that root **once**: `cwd: sub` runs in
|
|
566
|
+
`<bundle>/sub`, and an absolute `cwd` replaces the root. Agent and SDK units
|
|
567
|
+
get the same directory as their workspace, and so does a step's gate judge. If the workflow's bundle no longer
|
|
568
|
+
resolves, the step fails with a message naming the ref; a step with a gate is
|
|
569
|
+
blocked by its judge first, for `akm workflow resume`, because the judge is built
|
|
570
|
+
before anything is dispatched. akm never falls back to its own working
|
|
571
|
+
directory.
|
|
572
|
+
|
|
573
|
+
### The exec contract
|
|
574
|
+
|
|
575
|
+
What a command can rely on, in one place. The sections below give each point in
|
|
576
|
+
full.
|
|
577
|
+
|
|
578
|
+
| | |
|
|
579
|
+
| --- | --- |
|
|
580
|
+
| **Working directory** | The owner root of the run's workflow, found at each step's dispatch. `cwd` joins it once and may be absolute. |
|
|
581
|
+
| **Argv** | Spawned directly, never shell-interpreted. A `${ref}` token in an entry is replaced at dispatch. |
|
|
582
|
+
| **Environment** | Starts empty: the [default allowlist](#the-childs-environment-is-an-allowlist) plus `pass_env`, then the unit's `env:` bindings, then the engine's `AKM_*` [context](#context-reaching-the-command). |
|
|
583
|
+
| **`${ref}` tokens** | In argv, `cwd` and env-asset values. `${secrets/name}` becomes the secret's value, any other asset ref the asset's absolute path. A ref no bundle owns fails the unit before it spawns. Prompts are not substituted. |
|
|
584
|
+
| **Redaction** | Only values substituted from `secrets/` refs, engine credentials and unsafe passthrough values are scrubbed from stdout, stderr, diagnostics and the journal. Configuration, such as a URL bound through an env asset, is not. |
|
|
585
|
+
|
|
586
|
+
#### What is frozen, and what is found at dispatch
|
|
587
|
+
|
|
588
|
+
The frozen plan records the identities the engine itself dereferences: the
|
|
589
|
+
workflow ref, each unit's `env:` refs qualified with their bundle
|
|
590
|
+
(`team//env/prod`), and child workflow refs, plus the argv, `cwd` and
|
|
591
|
+
`pass_env` names as authored. It records no directory or file location. The
|
|
592
|
+
bundle root, the env files, the secrets and the paths behind `${ref}` tokens are
|
|
593
|
+
all looked up when the unit dispatches, through the same ref lookup `akm show`
|
|
594
|
+
uses. Plans frozen by earlier releases (`irVersion` 5 and 6) also carry a frozen
|
|
595
|
+
`cwdIdentity` and the env asset's owner paths; they decode, those fields are
|
|
596
|
+
ignored, and such a run dispatches by ref like any other.
|
|
597
|
+
|
|
598
|
+
#### `${ref}` in argv and `cwd`
|
|
599
|
+
|
|
600
|
+
An argv entry or `cwd` can name an asset in any bundle with the same
|
|
601
|
+
`${[bundle//]conceptId}` token env assets use:
|
|
602
|
+
|
|
603
|
+
```yaml
|
|
604
|
+
- id: status
|
|
605
|
+
unit:
|
|
606
|
+
env: [team//env/discord]
|
|
607
|
+
exec:
|
|
608
|
+
command: ["bun", "${homelab//scripts/lab-status/fetch-status.ts}"]
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
A `secrets/` ref gives the secret's value, trimmed of surrounding whitespace,
|
|
612
|
+
and joins the redaction set; any other asset ref gives the asset's absolute
|
|
613
|
+
path. Putting a secret in argv is the author's choice (it shows up in the
|
|
614
|
+
process list); prefer an `env:` binding. A `${...}` body that is not an akm ref
|
|
615
|
+
(`${HOME}`) is left as text. A ref no bundle owns fails the unit, naming it,
|
|
616
|
+
before anything is spawned.
|
|
562
617
|
|
|
563
618
|
### The output rule
|
|
564
619
|
|
|
@@ -623,13 +678,12 @@ failure reason.
|
|
|
623
678
|
| non-zero exit | `non_zero_exit` | yes |
|
|
624
679
|
| exceeded `timeout` | `timeout` | yes |
|
|
625
680
|
| run cancelled (`Ctrl-C`, `--timeout`, budget) | `aborted` | yes |
|
|
626
|
-
| binary missing / working directory
|
|
681
|
+
| binary missing / working directory missing | `spawn_failed` | yes |
|
|
627
682
|
| **stdout** capture never completed | `exec_capture_incomplete` | **no** (the command already ran) |
|
|
628
683
|
| **stderr** capture never completed | — (unit succeeds; a warning says the stderr tail may be missing) | — |
|
|
629
684
|
| stdout past the retention limit, **and** the unit declares `output:` | `exec_output_limit` | **no** (deterministic) |
|
|
630
685
|
| stdout past the retention limit, no `output:` schema | — (unit succeeds; artifact marked truncated) | — |
|
|
631
686
|
| `AKM_*` context too large for **this platform** to spawn | `exec_context_too_large` | **no** (an authoring/data problem) |
|
|
632
|
-
| `cwd` resolved outside its base | `exec_cwd_escape` | **no** (tampering, never transient) |
|
|
633
687
|
|
|
634
688
|
`non_zero_exit` in `retry.on` re-runs a command that already ran, and a
|
|
635
689
|
command's exit is usually deterministic, so the retry mostly repeats the
|
|
@@ -689,9 +743,10 @@ want the whole thing, have it write to a file and print the **path**:
|
|
|
689
743
|
|
|
690
744
|
### Context reaching the command
|
|
691
745
|
|
|
692
|
-
A frozen argv
|
|
693
|
-
|
|
694
|
-
|
|
746
|
+
A frozen argv never carries run data (this format has no substitution language
|
|
747
|
+
for params or step output; the only substitution is a [`${ref}` token](#ref-in-argv-and-cwd)
|
|
748
|
+
naming an asset), so data reaches an exec unit as **environment**, the argv
|
|
749
|
+
analogue of the context blocks a model unit gets in its prompt:
|
|
695
750
|
|
|
696
751
|
| Variable | Value |
|
|
697
752
|
| --- | --- |
|
|
@@ -773,8 +828,8 @@ an explicit error is the smaller, more predictable surface.
|
|
|
773
828
|
An exec unit's command does **not** inherit akm's environment. The child starts
|
|
774
829
|
from an **empty** environment and is built in three layers, in this order:
|
|
775
830
|
|
|
776
|
-
1. the **default allowlist** below, copied through
|
|
777
|
-
(plus any names the unit adds with `pass_env:`);
|
|
831
|
+
1. akm's own directories and the **default allowlist** below, copied through
|
|
832
|
+
from akm's own environment (plus any names the unit adds with `pass_env:`);
|
|
778
833
|
2. the unit's resolved **`env:` bindings**;
|
|
779
834
|
3. the engine-authored **`AKM_*` context** — last, so a binding can never
|
|
780
835
|
shadow it.
|
|
@@ -798,6 +853,14 @@ environment is simply absent from the child):
|
|
|
798
853
|
when akm is running in a scheduler context (cron/launchd/Task Scheduler) that
|
|
799
854
|
stripped it — the same treatment an agent-harness child gets.
|
|
800
855
|
|
|
856
|
+
The child also gets akm's own directories, whatever the parent found them
|
|
857
|
+
through: `AKM_CONFIG_DIR`, `AKM_DATA_DIR`, `AKM_CACHE_DIR` and `AKM_STATE_DIR` are
|
|
858
|
+
set to the directories the akm that started it resolved (from `AKM_*_DIR`,
|
|
859
|
+
`XDG_*_HOME` or the platform default), and `AKM_BUNDLE_DIR` is passed when that
|
|
860
|
+
akm had one. A step that runs `akm` therefore uses the same bundle, config, data,
|
|
861
|
+
cache and state, with no `pass_env:`. Naming one of these in `pass_env:` or
|
|
862
|
+
binding it in `env:` overrides it.
|
|
863
|
+
|
|
801
864
|
Deliberately **not** on the list: credentials of any kind, cloud/CI variables,
|
|
802
865
|
and the proxy family (`HTTP_PROXY` and friends — proxy URLs routinely embed
|
|
803
866
|
credentials). Reach a required value with an exact named `env:` binding, or
|
|
@@ -818,16 +881,16 @@ a committed *value*, so it cannot carry "whatever this build agent's
|
|
|
818
881
|
`CARGO_HOME` happens to be".
|
|
819
882
|
|
|
820
883
|
Values passed through this way are **not** redacted from the command's output
|
|
821
|
-
the way `env:`
|
|
884
|
+
the way a secret bound through `env:` is, so never list a credential here.
|
|
822
885
|
|
|
823
886
|
#### Binding a secret to a unit
|
|
824
887
|
|
|
825
888
|
A unit's `env:` lists env assets by ref. A value inside an env asset can be a
|
|
826
|
-
`${
|
|
889
|
+
`${ref}` token, which akm replaces with the secret's value when the unit is
|
|
827
890
|
dispatched. Say the bundle `team` holds `env/discord.env`:
|
|
828
891
|
|
|
829
892
|
```sh
|
|
830
|
-
DISCORD_BOT_TOKEN=${
|
|
893
|
+
DISCORD_BOT_TOKEN=${team//secrets/discord_bot_token}
|
|
831
894
|
```
|
|
832
895
|
|
|
833
896
|
and the secret beside it, set with
|
|
@@ -843,33 +906,42 @@ env asset:
|
|
|
843
906
|
command: ["node", "announce.mjs"]
|
|
844
907
|
```
|
|
845
908
|
|
|
846
|
-
-
|
|
847
|
-
|
|
848
|
-
bundle
|
|
849
|
-
|
|
909
|
+
- The token body is an akm ref, `[bundle//]conceptId`, resolved with the one
|
|
910
|
+
ref walk: `${team//secrets/discord_bot_token}` reads that secret from the
|
|
911
|
+
`team` bundle whichever bundle holds the env asset, and an unqualified
|
|
912
|
+
`${secrets/discord_bot_token}` resolves by the default bundle then
|
|
913
|
+
installation priority. A `secrets/...` ref gives the secret's value, trimmed
|
|
914
|
+
of surrounding whitespace; any other asset ref (`${scripts/x.sh}`) gives the
|
|
915
|
+
asset's absolute path. A `${...}` body that is not an akm ref (`${HOME}`) is
|
|
916
|
+
left as text.
|
|
850
917
|
- It is the same token `akm env run` resolves. `akm env export` does not
|
|
851
918
|
resolve it; it writes the token verbatim.
|
|
852
|
-
- Freezing records the env ref,
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
919
|
+
- Freezing records the env ref, never a value. At each dispatch the unit's env
|
|
920
|
+
assets are read live with `resolveEnvBinding`, the same function `akm env run`
|
|
921
|
+
uses, so an edit to the env file, or to a secret, after the run started is
|
|
922
|
+
picked up by the next dispatch; there is no key-set check against the frozen
|
|
923
|
+
plan. A ref no bundle owns fails the step at dispatch ("frozen environment
|
|
924
|
+
preflight failed") and nothing is injected.
|
|
856
925
|
- Each dispatch that reads the env asset records an `env_access` event with the
|
|
857
|
-
ref
|
|
858
|
-
-
|
|
859
|
-
redacted out of stdout, stderr and
|
|
860
|
-
|
|
861
|
-
|
|
926
|
+
ref and key names, never values.
|
|
927
|
+
- Redaction covers secrets, not configuration. A value substituted from a
|
|
928
|
+
`secrets/` ref is treated as sensitive and redacted out of stdout, stderr and
|
|
929
|
+
diagnostics before they are journaled. The rest of an env asset, such as a URL
|
|
930
|
+
or a channel id, reaches the journal and the next step unredacted. A
|
|
931
|
+
credential typed straight into an env file is not redacted, so keep
|
|
932
|
+
credentials in `secrets/` and bind them with a `${secrets/name}` token.
|
|
862
933
|
- `env:` needs a child process: it works on exec units and on agent and SDK
|
|
863
934
|
engines. A unit on an `llm` engine that declares `env:` fails
|
|
864
935
|
`env_unsupported`.
|
|
865
936
|
|
|
866
|
-
#### Durable v4 (`irVersion:
|
|
937
|
+
#### Durable v4 (`irVersion: 7`) forbids `inherit_env`
|
|
867
938
|
|
|
868
939
|
Every new workflow start freezes the durable plan v4 family's current
|
|
869
|
-
executable format
|
|
870
|
-
request for whole-process inheritance; use
|
|
871
|
-
and `pass_env:` instead. Both mechanisms are
|
|
872
|
-
visible environment surface bounded, and form
|
|
940
|
+
executable format (`irVersion: 7`; 5 and 6 still decode). It rejects
|
|
941
|
+
`inherit_env: true` and any other request for whole-process inheritance; use
|
|
942
|
+
exact named environment bindings and `pass_env:` instead. Both mechanisms are
|
|
943
|
+
dispatch-significant, keep the visible environment surface bounded, and form
|
|
944
|
+
part of the unit's input hash.
|
|
873
945
|
|
|
874
946
|
The historical `inherit_env` spelling is unsupported.
|
|
875
947
|
|
|
@@ -941,8 +1013,9 @@ They do not widen it, and they do not narrow it:
|
|
|
941
1013
|
the ref names, the replay hash carries only the ref names, and the resolved
|
|
942
1014
|
values are collected and scrubbed out of stdout, stderr, and the failure
|
|
943
1015
|
diagnostic by the same redaction contract every other dispatch uses — before
|
|
944
|
-
anything is journaled. Never inline a secret into `command:`; argv is
|
|
945
|
-
|
|
1016
|
+
anything is journaled. Never inline a secret value into `command:`; argv is
|
|
1017
|
+
stored as authored in `plan_json`. A `${secrets/name}` token in argv is
|
|
1018
|
+
stored as the token and replaced only when the unit dispatches.
|
|
946
1019
|
- **Read a workflow before you run it.** `exec:` makes what a workflow will run
|
|
947
1020
|
explicit and auditable in one place, which is a real improvement over
|
|
948
1021
|
instructing a model to "run the tests" — but a bundle you do not trust is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.261013-beta",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cursor.",
|
|
6
6
|
"keywords": [
|
package/schemas/akm-task.json
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
"maxStringUtf8Bytes": 262144,
|
|
10
10
|
"maxJsonDepth": 64,
|
|
11
11
|
"maxJsonNodes": 10000,
|
|
12
|
-
"canonicalRefsRequireNfc": true
|
|
13
|
-
"workingDirectoryRequiresWorkspaceRoot": true
|
|
12
|
+
"canonicalRefsRequireNfc": true
|
|
14
13
|
},
|
|
15
14
|
"type": "object",
|
|
16
15
|
"required": ["version"],
|
|
@@ -55,8 +54,7 @@
|
|
|
55
54
|
"working-directory": {
|
|
56
55
|
"type": "string",
|
|
57
56
|
"minLength": 1,
|
|
58
|
-
"maxLength": 262144
|
|
59
|
-
"pattern": "^(?![\\\\/])(?![A-Za-z]:[\\\\/])(?!.*(?:^|[\\\\/])\\.\\.(?:[\\\\/]|$))(?!.*[\\\\/]{2})(?!.*[\\\\/]$)[^\\u0000]+$"
|
|
57
|
+
"maxLength": 262144
|
|
60
58
|
},
|
|
61
59
|
"schedule": {
|
|
62
60
|
"oneOf": [
|
|
@@ -1,134 +0,0 @@
|
|
|
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
|
-
function scanStashForDangerousKeys(installedStashRoot) {
|
|
70
|
-
const scanner = scannerOverride ??
|
|
71
|
-
((envPath, relPath) => scanEnvKeyNames(fs.readFileSync(envPath, "utf8"))
|
|
72
|
-
.filter(isDangerousEnvKey)
|
|
73
|
-
.map((key) => ({ file: relPath, detail: `Env key \`${key}\`` })));
|
|
74
|
-
return collectDangerousKeyFindings(installedStashRoot, scanner);
|
|
75
|
-
}
|
|
76
|
-
export async function auditStashForDangerousKeys(opts) {
|
|
77
|
-
let allFindings;
|
|
78
|
-
try {
|
|
79
|
-
allFindings = scanStashForDangerousKeys(opts.stashRoot);
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
const rollbackWarning = await opts.rollback?.();
|
|
83
|
-
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");
|
|
84
|
-
}
|
|
85
|
-
const stance = decideDangerousKeyInstall({
|
|
86
|
-
findingsPresent: allFindings.length > 0,
|
|
87
|
-
allowDangerousEnvKeys: opts.allowDangerousKeys,
|
|
88
|
-
});
|
|
89
|
-
if (stance === "allow")
|
|
90
|
-
return { blocked: false, findings: allFindings };
|
|
91
|
-
if (stance === "warn-allow") {
|
|
92
|
-
for (const finding of allFindings) {
|
|
93
|
-
warn(`[dangerous-env-key] ${finding.relPath}: key \`${finding.keyName}\` in ${finding.envRef} can hijack process execution via \`akm env run\`. Proceeding because --allow-dangerous-env-keys was set.`);
|
|
94
|
-
}
|
|
95
|
-
return { blocked: false, findings: allFindings };
|
|
96
|
-
}
|
|
97
|
-
const operationTitle = opts.operation === "install" ? "Install" : "Update";
|
|
98
|
-
const operationPast = opts.operation === "install" ? "installed" : "updated";
|
|
99
|
-
let error;
|
|
100
|
-
if (opts.isTTY) {
|
|
101
|
-
const groupedByEnv = new Map();
|
|
102
|
-
for (const finding of allFindings) {
|
|
103
|
-
const existing = groupedByEnv.get(finding.envRef) ?? [];
|
|
104
|
-
existing.push(finding.keyName);
|
|
105
|
-
groupedByEnv.set(finding.envRef, existing);
|
|
106
|
-
}
|
|
107
|
-
for (const [envRef, keys] of groupedByEnv) {
|
|
108
|
-
warn(`[warn] Env "${sanitizeString(envRef)}" in stash "${sanitizeString(opts.ref)}" contains potentially dangerous keys:`);
|
|
109
|
-
for (const key of keys)
|
|
110
|
-
warn(` - ${sanitizeString(key)}: can hijack process execution via \`akm env run\``);
|
|
111
|
-
}
|
|
112
|
-
const confirmed = await p.confirm({ message: `${operationTitle} anyway?`, initialValue: false });
|
|
113
|
-
if (!p.isCancel(confirmed) && confirmed === true)
|
|
114
|
-
return { blocked: false, findings: allFindings };
|
|
115
|
-
error = `${operationTitle} aborted: stash contains dangerous env keys. Remove the keys or re-run with --allow-dangerous-env-keys to bypass.`;
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
const keyList = allFindings.map((finding) => ` - ${finding.keyName} (${finding.envRef})`).join("\n");
|
|
119
|
-
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-dangerous-env-keys to bypass this check after reviewing the env file.`;
|
|
120
|
-
}
|
|
121
|
-
const rollbackWarning = await opts.rollback?.();
|
|
122
|
-
if (opts.renderBlockedError !== false) {
|
|
123
|
-
console.error(JSON.stringify({
|
|
124
|
-
ok: false,
|
|
125
|
-
error,
|
|
126
|
-
code: "DANGEROUS_ENV_KEY",
|
|
127
|
-
...(rollbackWarning ? { rollbackWarning } : {}),
|
|
128
|
-
}, null, 2));
|
|
129
|
-
}
|
|
130
|
-
if (rollbackWarning) {
|
|
131
|
-
warn(`The blocked ${opts.operation} could not be fully rolled back after content was ${operationPast}: ${rollbackWarning}`);
|
|
132
|
-
}
|
|
133
|
-
return { blocked: true, exitCode: 1, code: "DANGEROUS_ENV_KEY", error, findings: allFindings };
|
|
134
|
-
}
|