You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ba7894d
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/reference/bundles.md
+5Lines changed: 5 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -335,6 +335,11 @@ non-default source.
335
335
336
336
> **Vet both the bundle source and its component catalogs.** A project can supply its own bundle catalog and `install-allowed` component catalogs (extension, preset, workflow, and step) under `.specify/`; that project configuration existing is not evidence anything in it was reviewed. Before installing a bundle from an unfamiliar project, run `specify bundle catalog list` — and the equivalent `extension`/`preset`/`workflow catalog list` and `specify workflow step catalog list` commands for the components it pulls in — and treat any source you didn't add yourself as unvetted until you've reviewed it.
337
337
338
+
For workflow components, inspect shell `run` and `env` fields and their
339
+
referenced code, including changes introduced by active overlays. This applies
340
+
even to `strict: true` steps: environment bindings can change executable lookup
341
+
or runtime loading. See [Reviewing shell execution](workflows.md#reviewing-shell-execution).
Copy file name to clipboardExpand all lines: docs/reference/workflows.md
+85-3Lines changed: 85 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -453,7 +453,7 @@ referents, so a pre-existing cache-file symlink can be replaced without changing
453
453
its target. If a cache cannot be safely written, a warning is logged and
454
454
discovery continues using the fetched catalog.
455
455
456
-
> **A project's `.specify/workflow-catalogs.yml` can point `add` and `search` at a catalog you didn't choose.** Before running a workflow from an unfamiliar project, run `specify workflow catalog list` (and `specify workflow step catalog list` for the step catalogs its steps can pull in) — a project supplying that config is not evidence its workflows or steps were vetted. Maintainers do not audit `run` fields; read a workflow's shell steps yourself before running it (see [Who maintains workflows?](#who-maintains-workflows)).
456
+
> **A project's `.specify/workflow-catalogs.yml` can point `add` and `search` at a catalog you didn't choose.** Before running a workflow from an unfamiliar project, run `specify workflow catalog list` (and `specify workflow step catalog list` for the step catalogs its steps can pull in) — a project supplying that config is not evidence its workflows or steps were vetted. Maintainers do not audit workflow code; review both `run` and `env` in its shell steps yourself, including strict steps (see [Reviewing shell execution](#reviewing-shell-execution) and [Who maintains workflows?](#who-maintains-workflows)).
457
457
458
458
### List Catalogs
459
459
@@ -782,6 +782,32 @@ qualified `step_id`. A step emits completion after its checkpoint; containers
782
782
emit before their children, while replayed completed occurrences emit no step
783
783
events.
784
784
785
+
### Shell step
786
+
787
+
`type: shell` executes `run` through the platform shell, retaining support for
788
+
pipes, redirects, and multiple commands. Set `strict: true` to reject `{{`
789
+
expression markers anywhere in `run`, including inside quotes. This is checked
790
+
both during validation and immediately before execution. `strict` must be a
791
+
boolean; omitted or `false` preserves existing raw interpolation behavior.
792
+
793
+
Use an `env` mapping to bind dynamic values without inserting them into shell
794
+
source. For example, on POSIX:
795
+
796
+
```yaml
797
+
- id: display-result
798
+
type: shell
799
+
strict: true
800
+
env:
801
+
RESULT: "{{ steps.plan.output.stdout }}"
802
+
run: 'printf "%s\n" "$RESULT"'
803
+
```
804
+
805
+
For cross-platform scripts, read the environment in the program itself, such
806
+
as `os.environ["RESULT"]` in Python. See
807
+
[Shell Step Environment Variables](#shell-step-environment-variables) for the
808
+
binding contract and [Interpolation and shell safety](#interpolation-and-shell-safety)
809
+
for the remaining trust boundary.
810
+
785
811
> **Security note:** a `shell` step runs a local command with **your** privileges. There is no capability sandbox — `requires` is an advisory pre-condition block (spec-kit version, integrations), not a runtime gate, so it does **not** restrict what a step can do. In particular there is no `requires.permissions` capability gate: it is rejected by validation precisely because it would imply a sandbox that does not exist. Review any catalog or downloaded workflow before running it, and use a `gate` step to require explicit approval before sensitive or destructive shell commands.
786
812
787
813
### GitHub step
@@ -1237,9 +1263,11 @@ If an interpolated value can contain characters like `;`, `|`, `&`, `$( )`, back
1237
1263
- **Workflow `inputs.*`** — supplied by whoever runs the workflow.
1238
1264
- **A prior step's output**, e.g. `{{ steps.plan.output.stdout }}` — for a `command` or `prompt` step with [`capture: true`](#capturing-command-and-prompt-output) this is **text produced by the AI agent**, which can in turn be influenced by files, tickets, or web content the agent read. Treat agent output as untrusted when it flows into a `shell` step.
1239
1265
1240
-
There is **no shell-escaping filter** in the expression language and **no sandbox** around a `shell` step, so none of the practices below can be treated as a guarantee that a hostile value is neutralised. The only reliable control is to constrain what an interpolated value *can* be, and to keep values you cannot constrain out of `run` fields entirely. Scrutinise every `run` field that interpolates a value you do not control, and at minimum:
1266
+
There is **no shell-escaping filter** in the expression language and **no sandbox** around a `shell` step. Prefer [`strict: true`](#shell-step) with `env` bindings to keep dynamic values out of shell source. Strict mode does not sanitise data, inspect commands, or prevent an authored script from interpreting data as code. Do not use `eval`, build another `sh -c` command from a variable, or pass untrusted values through shell startup variables such as `BASH_ENV`. On Windows, `cmd.exe` expansion of `%VALUE%` can introduce shell syntax; read untrusted values inside a program instead. On POSIX, quote variable expansions and use the target program's argument validation or `--` where appropriate to prevent option injection.
1241
1267
1242
-
- **Constrain the value at the source with `enum`/an allowlist.** When `inputs.*` feeds a `run` field, restrict it to a fixed set of known-safe values so a caller cannot supply arbitrary shell text at all. This is the strongest control the engine offers — prefer it over any downstream mitigation.
1268
+
For compatibility, raw interpolation remains enabled when `strict` is omitted or `false`. Its injection risk remains unchanged. Keep values you cannot constrain out of `run` fields entirely. Scrutinise every non-strict `run` field that interpolates a value you do not control, and at minimum:
1269
+
1270
+
- **Constrain the value at the source with `enum`/an allowlist.** When `inputs.*` feeds a non-strict `run` field, restrict it to a fixed set of known-safe values so a caller cannot supply arbitrary shell text at all.
1243
1271
1244
1272
```yaml
1245
1273
inputs:
@@ -1262,6 +1290,60 @@ Shell steps automatically receive the following environment variables:
1262
1290
| -------- | ----------- |
1263
1291
| `SPECKIT_WORKFLOW_DIR` | Resolved absolute path to the workflow source directory (same value as `{{ context.workflow_dir }}`). Not set when the workflow has no source path. |
1264
1292
1293
+
An optional per-step `env` mapping is available in both strict and non-strict
1294
+
mode. Names must match `[A-Za-z_][A-Za-z0-9_]*`; names are literal, not
1295
+
expressions. Values must be strings and may contain workflow expressions.
1296
+
Each value is evaluated once and must resolve to a string without NUL
1297
+
characters. On POSIX, the string must also be encodable for the process
1298
+
environment using the filesystem encoding and its error handler.
1299
+
Invalid bindings fail the step before launching a process.
1300
+
1301
+
Single-expression bindings preserve the expression's result type:
1302
+
`COUNT: "{{ inputs.count }}"` rejects a number, boolean, null, list, or mapping
1303
+
instead of converting it to text. Mixed templates use standard expression
1304
+
interpolation: `COUNT: "n={{ inputs.count }}"` produces `n=42` for integer
1305
+
`42`; embedded null becomes empty text, and other values use their string
1306
+
representation. To serialize structured data explicitly, use `to_json`.
1307
+
1308
+
Bindings override inherited environment values for that subprocess only;
1309
+
they do not modify the parent process or subsequent steps. An empty string is
1310
+
valid. `SPECKIT_WORKFLOW_DIR` is reserved (case-insensitively) and cannot be
1311
+
overridden. Keep environment names and executable-loading settings under the
1312
+
workflow author's control: environment binding is a data channel, not a sandbox.
1313
+
1314
+
### Reviewing shell execution
1315
+
1316
+
Before installing or running an unfamiliar workflow, audit each shell step's
1317
+
`run`**and** `env`, including nested branches and fan-out templates. Review
1318
+
referenced scripts and resources, called workflows, and active overlay YAML
1319
+
that can replace or insert steps. Apply the same review to workflow components
1320
+
of bundles, and repeat it when definitions, overlays, or referenced code change.
1321
+
Custom step packages require their own source review because loading them
1322
+
executes Python code.
1323
+
1324
+
An environment value can control execution rather than merely supply data.
1325
+
For example, `PATH` selects executables, `PYTHONPATH` affects Python imports,
1326
+
`BASH_ENV`can select Bash startup code, and `NODE_OPTIONS` can change Node.js
1327
+
startup behavior. Native loaders and other programs have their own settings;
1328
+
these examples are not an exhaustive list. A fixed `run` can therefore execute
1329
+
different code even with `strict: true` and no `eval`. Strict mode neither
1330
+
filters execution-controlling bindings nor cleans the inherited environment.
1331
+
Keep those settings literal and author-vetted, or constrain dynamic values to
1332
+
an approved set of trusted paths/options. Use unconstrained input or agent
1333
+
output only for application-data variables whose consumers handle it as data.
1334
+
1335
+
When a shell step invokes Specify itself, catalog overrides such as
1336
+
`SPECKIT_WORKFLOW_CATALOG_URL`can also change where child commands discover
1337
+
or install components. Vet those bindings alongside the
0 commit comments