Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
139be2f
docs(flux3): FLUX 3 Image Klein audit and catalog design
dkackman Oct 16, 2026
67ab2a0
docs(flux3): Klein catalog implementation plan
dkackman Oct 16, 2026
86459df
feat(text_generation): chat_template_kwargs, revision and a list of i…
dkackman Oct 16, 2026
a213781
feat(text_generation): system_prompt from a Hub repo file at a pinned…
dkackman Oct 16, 2026
4c6adb3
fix(text_generation): refuse structural keys and non-scalars in chat_…
dkackman Oct 16, 2026
643227f
fix(text_generation): from_hub validates repo/file/key, public files …
dkackman Oct 16, 2026
66314b6
feat(flux3): Klein prompt-upsampler builtins on the pinned pup
dkackman Oct 16, 2026
c7ca24b
fix(text_generation): from_hub fails closed unless the Hub serves the…
dkackman Oct 16, 2026
68adce1
feat(flux3): Klein text-to-image template and model entry
dkackman Oct 16, 2026
c4d0fad
feat(flux3): upsample-then-generate template
dkackman Oct 16, 2026
d377e72
feat(flux3): image-edit and multi-reference templates
dkackman Oct 16, 2026
8a62a7e
feat(flux3): layout-with-boxes template
dkackman Oct 16, 2026
803203c
docs(flux3): Klein templates README
dkackman Oct 16, 2026
c3690b4
feat(plugin): flux3-klein composition skill
dkackman Oct 16, 2026
81069fc
fix(plugin): flux3-klein skill's reference-detail failure mode
dkackman Oct 16, 2026
f77e09c
fix(text_generation): gate and filter generate_kwargs; fail closed on…
dkackman Oct 16, 2026
e6baee5
fix(flux3): state the 4096 side as unchecked, the edit upsampler's im…
dkackman Oct 16, 2026
181554d
docs: the generate_kwargs gate, the from_hub fetch, and what the spec…
dkackman Oct 16, 2026
b9b997f
test(catalog): raise compact listing budget 10_850 -> 11_400 for FLUX…
dkackman Oct 16, 2026
ad23a85
test(text_generation): patch hf_pipeline and hub download once via fi…
dkackman Oct 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ The guides in `docs/`, by topic:

`.claude-plugin/marketplace.json` publishes the `dw` plugin in `plugins/dw/`: one
composition skill per model family (`minimax-h3`, `minimax-music3`, `ltx-2.5`,
`kandinsky-6`) that chooses a template for a request's shape and states the family's
hard rules, plus
`kandinsky-6`, `flux3-klein`) that chooses a template for a request's shape and
states the family's hard rules, plus
cross-cutting composition skills (`script-to-video`, `series-episodes`) - shapes above
the families that orchestrate the decision trees and cast consistency across multiple
generations. Every skill the directory holds is named in `plugins/dw/README.md` and here,
Expand Down
2 changes: 2 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ one to open.
| Locations from a workflow | `dw/locations.py` | A local path in a workflow's media arguments (named like their media, or declared by the task's `@register_command(media_arguments=...)`, read through `TASK_MEDIA_ARGUMENTS`) is confined to a known root, a location written as a URL is one `security.validate_url` accepts, and a URL must not name a host inside the deployment. A host that does not resolve is refused. The requests themselves are `dw/outbound.py`'s. | `tests/test_locations.py::TestMediaPathContainment::test_absolute_path_outside_every_root_is_refused`, `tests/test_locations.py::TestMediaPathContainment::test_a_url_that_is_not_http_is_refused`, `tests/test_locations.py::TestMediaPathContainment::test_an_upper_case_https_url_is_not_refused_as_another_scheme`, `tests/test_security_ssrf.py::TestInternalAddressSpellings::test_a_name_that_resolves_inside`, `tests/test_locations.py::TestTaskMediaArguments::test_an_unreadable_source_is_an_error_at_its_path` |
| Outbound requests | `dw/outbound.py`: `safe_get`, `safe_post` | Every outbound request is validated per hop against `dw/locations.py`'s policy, resolved once and dialed at the address the policy checked, capped and timed. No other module under `dw/` makes an HTTP request or builds a session or adapter. | `tests/test_outbound.py::TestSafeRequest::test_one_lookup_per_hop_and_the_dial_is_the_checked_address` |
| Trust gate | `dw/trust.py`: `require_trusted_dotted_name` | In an untrusted workflow (the default), a dotted type outside the allowlist is a validation error before anything is imported. | `tests/test_security_trust_gate.py::TestValidationRefusesBeforeImport::test_a_dotted_type_is_a_validation_error` |
| Remote generate code | `dw/trust.py`: `require_trusted_generate_arguments`; `dw/tasks/text_generation.py`: `_validated_generate_kwargs`, `_validated_chat_template_kwargs` | Untrusted, `generate_kwargs` may not set `custom_generate` or `trust_remote_code` (transformers would download and run a repo's `custom_generate/generate.py`); both it and `chat_template_kwargs` hold only scalars (or, for `generate_kwargs`, lists of them) and none of the pipeline call's own keys. All checked before the model loads. | `tests/test_security_trust_gate.py::TestRemoteGenerateCodeUntrusted::test_refused_before_the_model_loads`, `tests/test_text_generation.py::TestGenerateKwargsAreFiltered::test_a_structural_key_is_refused_before_any_load`, `tests/test_text_generation.py::TestGenerateKwargsAreFiltered::test_a_nested_value_is_refused` |
| Hub-file system prompts | `dw/tasks/text_generation.py`: `resolve_system_prompt` | A `system_prompt.from_hub` file is pinned to a full commit, asked of the Hub anonymously before every use (failing closed on any error, offline or under `HF_HUB_OFFLINE`), downloaded with `token=False`, and refused over 1 MiB or when the Hub reports no size. | `tests/test_text_generation.py::TestSystemPromptFromHub::test_a_file_the_hub_will_not_serve_publicly_is_refused`, `tests/test_text_generation.py::TestSystemPromptFromHub::test_the_download_never_carries_the_server_token`, `tests/test_text_generation.py::TestSystemPromptFromHub::test_a_file_of_unknown_size_is_refused_before_downloading` |
| CodeQL path-injection model | `.github/codeql/dw-security/DwPathSanitizers.qll`, `.github/workflows/codeql.yml` | The local pack models the `dw/security.py` validators (and `dw/locations.py`'s `validate_media_path`) as sanitizers, so a validator that moves is re-modelled in the same commit. | `.github/codeql/dw-security/DwPathInjection.ql` |
| Archives never follow links | `dw/server/outputs.py`: `zip_download` | A gallery listing drops a symlink that leaves its root, and an archive skips one. | `tests/test_security_symlinks.py::TestOutputs::test_the_archive_route_does_not_follow_the_link`, `tests/test_security_symlinks.py::TestOutputs::test_the_gallery_listing_does_not_enumerate_the_link` |
| Engine/server import direction | `dw/workspace.py`: `EXPORTS_SUBDIR` | No engine module imports `dw.server` except the entry point `dw/serve.py`, and a constant both sides need lives engine-side. | `docs/stabilization/baseline.json`: `import_cycles` ratchet (indirect: it fails only on an import that closes a cycle) |
Expand Down
20 changes: 20 additions & 0 deletions docs/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,26 @@ diffusers' remote-code paths open. No bundled catalog entry sets either
(`tests/test_catalog_structure.py` refuses one that does); a workflow that
needs them needs `--trust-workflows`.

`text_generation`'s `generate_kwargs` reach the model's `generate()`, which
has a remote-code path of its own: `custom_generate` names a Hub repo whose
`custom_generate/generate.py` transformers downloads and runs, and
`trust_remote_code` lets it. Both are refused untrusted
(`require_trusted_generate_arguments` in `dw/trust.py`), before the model
loads. Its other keys must be scalars or lists of them, and none may be one of
the pipeline call's own arguments - the same filter `chat_template_kwargs`
gets, so neither can pass `tokenizer_encode_kwargs`, `images` or a template
of its own around the task's checks.

`text_generation`'s `system_prompt` may be read from a Hub repo file
(`{"from_hub": {...}}`). That fetch is anonymous only: the Hub is asked
without credentials (`token=False`) whether it serves the file publicly at the
pinned 40-hex commit before every use, and the download carries no token
either, so the server's own login never reaches a gated or private file on a
workflow's say-so. It fails closed - any error from that ask, being offline,
or `HF_HUB_OFFLINE` refuses the run rather than fall back to the cache - and
it is capped at 1 MiB, checked against the Hub's reported size before the
download (no reported size is a refusal) and against the file after it.

### Where a workflow may read and reach (`dw/locations.py`)

Code execution is not the only thing a workflow file chooses. Its arguments
Expand Down
17 changes: 16 additions & 1 deletion docs/TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2848,8 +2848,21 @@ Generate or expand text using a local language model. Useful for expanding short
| `model_name` | No | HuggingFace model ID (default: `Qwen/Qwen2.5-1.5B-Instruct`, or `HuggingFaceTB/SmolVLM-256M-Instruct` when an image is supplied) |
| `image` | No | PIL Image, URL/path, or `previous_result:` reference — see below |
| `repetition_penalty` | No | Vision path only (default: 1.15) — see below |
| `generate_kwargs` | No | Anything else to pass to the model's `generate()` — `no_repeat_ngram_size`, `top_p`, `min_new_tokens`. Merged last, so it overrides the settings above |
| `generate_kwargs` | No | Anything else to pass to the model's `generate()` — `no_repeat_ngram_size`, `top_p`, `min_new_tokens`, `stop_strings`. Merged last, so it overrides the settings above. Filtered like `chat_template_kwargs` — see below |
| `max_new_tokens` | No | Maximum tokens to generate (default: 500) |
| `chat_template_kwargs` | No | Extra keyword arguments for the model's chat template (for a template that takes switches such as a thinking toggle). Values must be scalars (boolean, number, string or null), and the generation call's own keys (`images`, `tools`, `max_new_tokens`, `tokenizer_encode_kwargs`, ...) are refused |
| `revision` | No | Model revision (branch, tag or commit) to load `model_name` at |

`image` may also be a list of images, each sent as its own picture in the one user message. A list is one prompt over all the images, in order, and returns one answer — not one answer per image. The same holds for `judge` and `image_to_text`, which hand their `image` to this task. An empty list, or one holding only nulls, is no image.

`system_prompt` may name a file in a Hugging Face repo instead of carrying the text,
for a vendor that ships its prompt instructions there:
`{"from_hub": {"repo": "owner/name", "file": "system_prompts.json", "revision": "<40-hex commit>", "key": "t2i"}}`.
The revision must be a full commit, so the text can't move under the workflow. A `.json`
file takes `key` to pick one string; any other file is read whole. Only public repos are
reachable (the download never uses the server's Hugging Face token), and a file over 1 MiB
is refused. The Hub is asked, without credentials, whether the file is public before every
use, so `from_hub` needs network access even when the file is already cached.

### Writing a prompt from a picture

Expand Down Expand Up @@ -2890,6 +2903,8 @@ For anything the arguments above do not cover, `generate_kwargs` goes straight t

It is merged after everything else, so it can override `repetition_penalty` and the sampling settings as well as add to them.

`generate_kwargs` is checked before the model loads, the same way `chat_template_kwargs` is. Values must be scalars or lists of scalars (`stop_strings` is a list), and the call's own keys are refused (`max_new_tokens`, `tokenizer_encode_kwargs`, `prefix`, `handle_long_generation` and the rest of the pipeline's named arguments). `custom_generate` and `trust_remote_code` need trusted workflows (`--trust-workflows`): with them, transformers downloads a repo's `custom_generate/generate.py` and runs it in place of its own decoding loop.

**Examples:**

- [expand-prompt.json](../workflows/templates/expand-prompt.json) — Expand a short prompt and save as `.txt`
Expand Down
Loading
Loading