Skip to content

feat(harness): media feature with GenerateImageTool / GenerateVideoTool - #208

Merged
senamakel merged 83 commits into
mainfrom
media-openrouter
Sep 24, 2026
Merged

senamakel merged 83 commits into
mainfrom
media-openrouter

Conversation

@senamakel

@senamakel senamakel commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds a media feature to tinyagents-harness that exposes TinyInference's new generators (tinyhumansai/tinyinference#27) as ordinary tinytools::Tools:

  • GenerateImageTool over tinyinference_image::ImageGenerator.
  • GenerateVideoTool over tinyinference_video::VideoGenerator. It waits under a host WaitPolicy and supports resume_job_id, which collects a timed-out job without a second billed submit.
  • MediaOutput: artifacts go into the run's workspace (generated-media/). Local reference paths are confined to the workspace (no .., nothing outside the root) unless the host installs its own ReferencePathPolicy.
  • Re-exports and error mapping: tinyinference_image / tinyinference_video are re-exported when the feature is on, and both error types map into TinyAgentsError.
  • Host overrides: name, description, permission level, category and wait policy.
  • Tool policy: declares payment, network, external_service and writes_files, plus idempotent: false, so a host never replays a billed call after a crash.

The harness owns argument parsing (including the loose spellings and camelCase aliases models emit), artifact persistence and result wording. The host owns the generator, which carries the credential and endpoint.

Why

This is the harness half of fixing a billed non-delivery in OpenHuman: an image request failed three times and was charged each time. Each tool reports a billed non-delivery or timeout as an error that names the job and says not to retry.

Tests

crates/tinyagents-harness/src/media/test.rs (10), including:

  • R2: billed_non_delivery_tells_the_model_not_to_retry and video_timeout_names_the_job_and_resume_collects_it (resume submits nothing new).
  • R1 through the tool: video_tool_waits_for_delivery_and_saves_the_clip, which polls through completed with no outputs.
  • Security: references_resolve_inside_the_workspace_and_are_confined_to_it and a_host_reference_policy_replaces_the_default_confinement.
  • Metadata: media_tools_declare_billing_side_effects and media_errors_map_onto_harness_errors.

Docs: docs/modules/harness/media.md, linked from the harness README and the spec index.

Verification

cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo clippy -p tinyagents-harness --features media --all-targets -- -D warnings
cargo test -p tinyagents-harness --features media     # 1271 + 43 doctests
cargo check -p tinyagents-harness --all-features

Merged latest main before pushing. Depends on tinyhumansai/tinyinference#27 (the vendor/tinyinference gitlink points at that branch); move the gitlink to the merge commit once it lands.

Summary by CodeRabbit

  • New Features
    • Added tools for generating images and videos, with configurable options and saved outputs in the workspace.
    • Added support for resuming video-generation jobs and applying host-defined policies to image references.
  • Documentation
    • Added guidance on configuring and using the media-generation tools.

The tinyinference submodule is updated to a newer commit, including local uncommitted changes. This keeps the vendored dependency in sync with the upstream repository.

Auto-committed-on: macbook
The Display implementation for media types was inadvertently removed during a refactor, causing compilation errors for code that relied on formatting these types. This change restores the trait implementation to its previous behavior.

Auto-committed-on: macbook
Introduce a new image tool module in the tinyagents-harness crate to support media-related image operations, enabling agents to handle image inputs and outputs within the harness framework.

Auto-committed-on: macbook
The video tool implementation was previously deleted, and this change restores it to the media module. The tool is needed again for handling video inputs in the harness, so the original functionality is brought back intact.

Auto-committed-on: macbook
When the media directory does not exist, the harness now creates it automatically instead of panicking. This improves robustness when running tests in clean environments where the directory has not been pre-created.

Auto-committed-on: macbook
Adds a test file for the media module to verify its behavior. This provides initial coverage for the media-related functionality in the harness crate.

Auto-committed-on: macbook
Add an optional `media` feature to the harness that enables image and video generation capabilities through the new `tinyinference-image` and `tinyinference-video` crates. This includes a new `media` module, re-exports of the underlying crates, and error conversion implementations that map their errors into the existing `TinyAgentsError` hierarchy.

Auto-committed-on: macbook
…nyagents-harness/src/lib.rs,cra

Auto-committed-on: macbook
Replace the pattern of creating a default VideoRequest and then mutating each field individually with a struct literal that sets all fields at once, using the spread operator to fill in any remaining defaults. This makes the construction more idiomatic and reduces the number of mutable assignments.

Auto-committed-on: macbook
Adds documentation for the harness media module, covering its purpose and usage.

Auto-committed-on: macbook
Add links to the new media generation documentation in both the harness module README and the spec README, so the feature is discoverable from the main documentation indexes.

Auto-committed-on: macbook
…urable

The image and video generation tools now accept overrides for their permission level and tool category, defaulting to write permission and the system category. This allows hosts to gate these tools differently depending on the calling context. The tinyinference submodule is also updated to a newer revision.

Auto-committed-on: macbook
Updated the pinned commit of the vendor/tinyinference subproject to include recent changes, with a dirty suffix indicating uncommitted modifications in the submodule.

Auto-committed-on: macbook
Updated the pinned commit for the tinytools vendored dependency to incorporate upstream fixes and improvements.

Auto-committed-on: macbook
@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 0be074d1-b2f7-414d-a292-d59ae2e5baa9

📥 Commits

Reviewing files that changed from the base of the PR and between 2f9e2eb and 6ac4ff2.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • crates/tinyagents-harness/Cargo.toml
  • crates/tinyagents-harness/src/error.rs
  • crates/tinyagents-harness/src/lib.rs
  • crates/tinyagents-harness/src/media/image_tool.rs
  • crates/tinyagents-harness/src/media/mod.rs
  • crates/tinyagents-harness/src/media/test.rs
  • crates/tinyagents-harness/src/media/types.rs
  • crates/tinyagents-harness/src/media/video_tool.rs
  • docs/modules/harness/README.md
  • docs/modules/harness/media.md
  • docs/spec/README.md
  • vendor/tinyinference
  • vendor/tinytools
 ______________________________________________________________________
< You used `any` like it's a life jacket. Spoiler: it's a pool noodle. >
 ----------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-24T06:11:05.061097Z 6ac4ff2 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1e5b648fb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/tinyagents-harness/src/media/types.rs
Comment thread crates/tinyagents-harness/src/media/image_tool.rs
Comment thread crates/tinyagents-harness/src/media/types.rs Outdated
@tinysweeper

tinysweeper Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

Tiny Sweeper reviewed this change across 6 lane(s) and found 10 active actionable finding(s). Detailed lane evidence and any incomplete work are listed below.

State: Incomplete
Priority: high
Reviewed head: 6ac4ff222ed1
Updated: 1790231373 (Unix time)

Review snapshot

Change surface Files Review signal Count
Production 6 Active findings 19
Tests 1 Noted findings 0
Documentation 3 Resolved findings 143
Configuration 1 Pending checks/questions 2

Completeness: Incomplete
Test assessment: No supported feature-to-test mapping was available; this does not mean tests are absent or passed.

What changed

The review could not produce a supported behavioral summary; inspect the cited changed surface and lane details below.

Features

None identified with supported citations.

Tests

No supported feature-to-test mapping was produced. Test execution is not inferred.

  • Unreviewed: tinysweeper/tests

Findings

  • high · critique · Keep reference validation atomic with provider reads — A local reference is validated and canonicalized here, but the resulting reference is then stored in `request` and read later by the provider. If an attacker can modify the workspa (crates/tinyagents\-harness/src/media/image\_tool\.rs:151)
  • medium · critique · Reject empty string list values — `check_string_lists` accepts both an empty scalar string and an empty array: the scalar matches `Value::String(_)`, and `all(...)` is true for an empty array. `arg_list` then drops (crates/tinyagents\-harness/src/media/types\.rs:384)
  • medium · critique · Name the exposed harness feature correctly — The harness exposes `multimodal`, not `media`, as its Cargo feature. A user following this documentation cannot enable the module with `--features media`; the documented feature mu (docs/modules/harness/media\.md:3)
  • medium · critique · Add direct coverage for image-tool boundary safeguards — This test file still does not exercise several externally visible image-tool safeguards through `execute`: the maximum image-count limit, unsupported response media types before pe (crates/tinyagents\-harness/src/media/test\.rs:575)
  • medium · critique · Reject empty string list values — `check_string_lists` accepts an empty scalar string, and its array branch also accepts an empty array because `all(...)` is vacuously true. `arg_list` then silently drops these val (crates/tinyagents\-harness/src/media/types\.rs:373)
  • medium · critique · Make artifact stems unique across processes — The counter only provides uniqueness within one process, and the timestamp has millisecond resolution. Two processes generating the same `kind` in the same shared workspace during (crates/tinyagents\-harness/src/media/mod\.rs:55)
  • medium · critique · Anchor non-Unix artifact writes to the verified directory — On non-Unix platforms, the write uses the previously constructed pathname after `dir` has validated it. Replacing the output directory with a junction, symlink, or another redirect (crates/tinyagents\-harness/src/media/types\.rs:228)
  • medium · critique · Reject unsigned values that cannot fit request fields — Numeric strings are parsed as unrestricted `u64` values here. Inputs such as `"18446744073709551615"` pass this check, so callers that map them to signed or narrower request fields (crates/tinyagents\-harness/src/media/types\.rs:343)
  • medium · critique · Anchor artifact writes to every verified ancestor — The directory is checked and canonicalized by pathname, then reopened by pathname. Between those operations, a process able to modify the output tree can replace an ancestor or the (crates/tinyagents\-harness/src/media/types\.rs:165)
  • high · security · Require artifact names to contain exactly one path component — `is_filename_component` accepts values such as `nested/file` because every component is `Normal`. The resulting filename is passed to `openat`; `NOFOLLOW` protects only the final c (crates/tinyagents\-harness/src/media/types\.rs:289)
  • high · security · Gate these conversions on the exposed media feature — The harness exposes `multimodal`, not `media`, as its media-related Cargo feature. This condition is therefore never enabled for media builds, so these `From` implementations are o (crates/tinyagents\-harness/src/error\.rs:357)
  • medium · security · Reject empty string list values — `check_string_lists` accepts any present string, including `""` and whitespace-only values, even though its contract says list entries must be non-empty. `arg_list` then silently d (crates/tinyagents\-harness/src/media/types\.rs:373)
  • medium · security · Reject empty string arrays before building requests — The predicate is vacuously true for an empty array, so `check_string_lists` accepts `[]`. `arg_list` then turns it into an empty vector, silently dropping the caller's option and a (crates/tinyagents\-harness/src/media/types\.rs:385)
  • medium · security · Add focused tests for image-tool boundary behavior — This change adds billing-sensitive and filesystem-sensitive guards, including maximum-count enforcement, empty billed responses, malformed options, media-type rejection, and refere (crates/tinyagents\-harness/src/media/image\_tool\.rs:320)
  • medium · security · Match the complete billed non-delivery error — This assertion only checks for two independent substrings, so a regression could change the request identifier, error classification, or wording while the test still passes. Assert (crates/tinyagents\-harness/src/media/test\.rs:182)
  • medium · security · Match the complete resumable-timeout error — The timeout regression test accepts any message containing the job id and a generic phrase. It does not verify the provider reason, timeout classification, or the exact resume/no-r (crates/tinyagents\-harness/src/media/test\.rs:449)
  • medium · security · Anchor every verified artifact ancestor — The verified directory is opened through an absolute path after each ancestor was checked separately. A concurrent local attacker can replace an ancestor of `canonical_dir` with a (crates/tinyagents\-harness/src/media/types\.rs:161)
  • medium · security · Fail closed when the reference root cannot be canonicalized — When canonicalizing `root` fails, this uses the uncanonicalized root for the confinement check while comparing it with a canonical reference path. That makes the authorization deci (crates/tinyagents\-harness/src/media/types\.rs:273)

Previously reported and still active

  • Reject non-string reference-list entries

Resolved this pass

  • Gate these conversions on the exposed media feature
  • Gate these conversions on the exposed media feature
  • Reject empty image responses as billed failures
  • Preserve the generated image format in artifact paths
  • Reject requests without a prompt or first frame
  • Enforce the maximum image count at runtime
  • Canonicalize reference paths to prevent symlink-based path traversal
  • Allow only safe image extensions before persisting
  • Reject malformed string options before billing
  • Reject non-string reference entries
  • Reject empty string list arguments
  • Constrain artifact subdirectories to the output root
  • Anchor non-Unix artifact writes to the verified directory
  • Assert that tilde-prefixed references stay inside the workspace
  • Fix test assertions to match actual error messages
  • Test symlinked references cannot escape the workspace
  • Construct the video media type in the WebM fixture
  • Preserve the generated image format in artifact paths
  • Tighten billed non-delivery assertion to match actual error text
  • Tighten timeout assertion to match actual error text
  • Canonicalize reference paths to prevent symlink-based path traversal
  • Reject an empty artifact subdirectory
  • Validate artifact names before joining them
  • Constrain artifact subdirectories to the output root
  • Reject empty video responses as billed failures
  • Reject requests without a prompt or first frame
  • Reject durations that overflow the provider field
  • Reject malformed string options before billing
  • Reject non-string reference entries
  • Reject non-string entries in reference lists
  • Validate resumed job identifiers before polling
  • Preserve the generated image format in artifact paths
  • Construct the video media type in the WebM fixture
  • Allow only safe image extensions before persisting
  • Constrain artifact subdirectories to the output root
  • Reject an empty artifact subdirectory
  • Validate artifact names before joining them
  • Canonicalize reference paths to prevent symlink-based path traversal
  • Resolve local references before admitting them
  • Keep reference validation atomic with the provider read
  • Test symlinked references cannot escape the workspace
  • Assert that tilde-prefixed references stay inside the workspace
  • Verify tilde paths after creating a literal workspace target
  • Verify that custom policies can admit outside paths
  • Use a path outside the workspace for policy override coverage
  • Anchor artifact writes to every verified ancestor
  • Anchor non-Unix artifact writes to the verified directory
  • Document the default reference policy accurately
  • Update documentation to reflect symlink limitation or fix the code
  • Reject image counts that cannot fit the request field
  • Enforce the maximum image count at runtime
  • Reject unsigned values outside the signed range
  • Reject durations outside the provider field range
  • Reject empty string list arguments
  • Reject empty string list values
  • Reject empty string list arguments
  • Reject empty image responses as billed failures
  • Fix billed non-delivery test to check the actual error text
  • Tighten billed non-delivery assertion to match actual error text
  • Gate these conversions on the exposed media feature
  • Add focused tests for image-tool boundary behavior
  • Honor the configured wait budget in the tool timeout policy
  • Document the video empty-result behavior accurately
  • Document billed empty video responses
  • Document the default reference policy accurately
  • Update documentation to reflect symlink limitation or fix the code
  • Qualify the workspace restriction for custom policies
  • Gate these conversions on the exposed media feature
  • Reject empty image responses as billed failures
  • Assert that tilde-prefixed references stay inside the workspace
  • Verify that custom policies can admit outside paths
  • Reject requests without a prompt or first frame
  • Preserve the generated image format in artifact paths
  • Resolve local references before admitting them
  • Enforce the maximum image count at runtime
  • Canonicalize reference paths to prevent symlink-based path traversal
  • Update documentation to reflect symlink limitation or fix the code
  • Test symlinked references cannot escape the workspace
  • Verify tilde paths after creating a literal workspace target
  • Constrain artifact subdirectories to the output root
  • Reject malformed string options before billing
  • Reject non-string reference entries
  • Reject an empty artifact subdirectory
  • Allow only safe image extensions before persisting
  • Reject empty string list arguments
  • Anchor artifact writes to every verified ancestor
  • Assert that tilde-prefixed references stay inside the workspace
  • Canonicalize reference paths to prevent symlink-based path traversal
  • Constrain artifact subdirectories to the output root
  • Reject an empty artifact subdirectory
  • Validate artifact names before joining them
  • Test symlinked references cannot escape the workspace
  • Anchor every verified ancestor
  • Reject requests without a prompt or first frame
  • Reject durations that overflow the provider field
  • Reject malformed string options before billing
  • Reject non-string reference entries
  • Reject empty video responses as billed failures
  • Validate resumed job identifiers before polling
  • Document the default reference policy accurately
  • Honor the configured video wait budget
  • Reject empty image responses as billed failures
  • Assert that tilde-prefixed references stay inside the workspace
  • Verify that custom policies can admit outside paths
  • Reject requests without a prompt or first frame
  • Preserve the generated image format in artifact paths
  • Resolve local references before admitting them
  • Enforce the maximum image count at runtime
  • Canonicalize reference paths to prevent symlink-based path traversal
  • Update documentation to reflect symlink limitation or fix the code
  • Test symlinked references cannot escape the workspace
  • Verify tilde paths after creating a literal workspace target
  • Constrain artifact subdirectories to the output root
  • Reject durations that overflow the provider field
  • Document the video empty-result behavior accurately
  • Limit the job-id guarantee to resumable video errors
  • Reject empty billed video responses
  • Allow only safe image extensions before persisting
  • Use a path outside the workspace for policy override coverage
  • Reject unsigned values outside the signed range
  • Document the default reference policy accurately
  • Reject empty string list arguments
  • Honor the configured video wait budget
  • Honor the configured wait timeout in the tool timeout policy
  • Do not fall back to an uncanonicalized reference root
  • Reject non-string reference entries
  • Reject an empty artifact subdirectory
  • Reject malformed string options before billing
  • Validate resumed job identifiers before polling
  • Reject non-string entries instead of dropping them
  • Gate these conversions on the exposed media feature
  • Keep reference validation atomic with the provider read
  • Document billed empty video responses
  • Reject durations outside the provider field range
  • Reject unsigned values outside the provider range
  • Reject image counts that cannot fit the request field
  • Anchor artifact writes to every verified ancestor
  • Anchor non-Unix artifact writes to the verified directory
  • Qualify the workspace restriction for custom policies
  • Add focused tests for image-tool boundary behavior
  • Reject empty string list values
  • Reject non-string entries in reference lists
  • Fix billed non-delivery test to check the actual error text

Could not review: tinysweeper/description, tinysweeper/tests

Before merge

  • Address carried finding Reject non-string reference-list entries.
  • Address Keep reference validation atomic with provider reads (crates/tinyagents\-harness/src/media/image\_tool\.rs).
  • Address Require artifact names to contain exactly one path component (crates/tinyagents\-harness/src/media/types\.rs).
  • Address Gate these conversions on the exposed media feature (crates/tinyagents\-harness/src/error\.rs).
  • Complete the tests review for tinysweeper/tests.
  • Complete the description review for tinysweeper/description.

How this fits together

flowchart LR
  n0["TinyAgentsError<br/>changed<br/>1 finding"]:::blocking
  n1["from_provider_error"]:::impacted
  n2["run_phase"]:::impacted
  n3["map_reduce"]:::impacted
  n4["is_context_overflow"]:::impacted
  n5["providers"]:::impacted
  n6["openai"]:::impacted
  n1 -->|uses| n5
  n1 -->|uses| n6
  n2 -->|uses| n0
  n2 -->|calls| n3
  n3 -->|uses| n0
  n4 -->|uses| n5
  n4 -->|uses| n6
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading
Agent review details

critique

  • Conclusion: Failure
  • Scope reviewed: all assigned evidence
  • Lane summary: Reviewed 11 files; 14 findings. (5 already reported on an earlier push) (6 earlier finding(s) still open) (1 observation(s) grouped into shared inline comments) _The code index is behind this pull request (indexed at `79e4b914410e`), so retrieved context may be out of date._ _2 memory call(s) failed (model: cortex: v1/answer: timed out after 20s), so this review saw part of what the engine holds._
  • Evidence: crates/tinyagents\-harness/src/media/image\_tool\.rs — Keep reference validation atomic with provider reads
  • Evidence: crates/tinyagents\-harness/src/media/types\.rs — Reject empty string list values
  • Evidence: docs/modules/harness/media\.md — Name the exposed harness feature correctly
  • Evidence: crates/tinyagents\-harness/src/media/test\.rs — Add direct coverage for image-tool boundary safeguards
  • Evidence: crates/tinyagents\-harness/src/media/types\.rs — Reject empty string list values
  • Evidence: crates/tinyagents\-harness/src/media/mod\.rs — Make artifact stems unique across processes
  • Evidence: crates/tinyagents\-harness/src/media/types\.rs — Anchor non-Unix artifact writes to the verified directory
  • Evidence: crates/tinyagents\-harness/src/media/types\.rs — Reject unsigned values that cannot fit request fields
  • Evidence: crates/tinyagents\-harness/src/media/types\.rs — Anchor artifact writes to every verified ancestor

security

  • Conclusion: Failure
  • Scope reviewed: all assigned evidence
  • Lane summary: Reviewed 8 files; 14 findings. 3 files were not security-reviewed: docs/modules/harness/README.md (prose or tabular data), docs/modules/harness/media.md (prose or tabular data), docs/spec/README.md (prose or tabular data). (5 already reported on an earlier push) (5 earlier finding(s) still open) _The code index is behind this pull request (indexed at `79e4b914410e`), so retrieved context may be out of date._ _2 memory call(s) failed (model: cortex: v1/answer: timed out after 20s), so this review saw part of what the engine holds._
  • Evidence: crates/tinyagents\-harness/src/media/types\.rs — Require artifact names to contain exactly one path component
  • Evidence: crates/tinyagents\-harness/src/error\.rs — Gate these conversions on the exposed media feature
  • Evidence: crates/tinyagents\-harness/src/media/types\.rs — Reject empty string list values
  • Evidence: crates/tinyagents\-harness/src/media/types\.rs — Reject empty string arrays before building requests
  • Evidence: crates/tinyagents\-harness/src/media/image\_tool\.rs — Add focused tests for image-tool boundary behavior
  • Evidence: crates/tinyagents\-harness/src/media/test\.rs — Match the complete billed non-delivery error
  • Evidence: crates/tinyagents\-harness/src/media/test\.rs — Match the complete resumable-timeout error
  • Evidence: crates/tinyagents\-harness/src/media/types\.rs — Anchor every verified artifact ancestor
  • Evidence: crates/tinyagents\-harness/src/media/types\.rs — Fail closed when the reference root cannot be canonicalized

tests

  • Conclusion: Neutral
  • Scope reviewed: incomplete; unanswered: tinysweeper/tests
  • Lane summary: No reviewer could be consulted.

commits

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: Nothing sensitive found in what this pull request commits.

description

  • Conclusion: Neutral
  • Scope reviewed: incomplete; unanswered: tinysweeper/description
  • Lane summary: No reviewer could be consulted.

e2e

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: No end-to-end harness in this repository: no e2e test files and no e2e workflow.
Evidence and run details
  • Models: ladder/vectors, gpt-5.6-luna, deepseek/deepseek-v4-flash
  • Spend: $0.084571
  • Tokens: 1373858 input · 64255 output · 86592 cached · 1081 embedding
Head State Pass summary
1ff76e73f6bf changes requested 17 active finding(s), 173 resolved finding(s) (at 1790226723)
0a8a55dd40c1 changes requested 23 active finding(s), 235 resolved finding(s) (at 1790228505)
0a8a55dd40c1 incomplete 16 active finding(s), 212 resolved finding(s) (at 1790228960)
6ac4ff222ed1 incomplete 19 active finding(s), 205 resolved finding(s) (at 1790230880)
6ac4ff222ed1 incomplete 18 active finding(s), 143 resolved finding(s) (at 1790231373)

tinysweeper 0.1.0

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes: 2 lane(s) blocking, worst finding is high.

Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.

             $0.0543 · 1,038,090 in / 44,953 out · 43,896 cached (4%) · ladder/vectors, gpt-5.6-luna, deepseek/deepseek-v4-flash, deepseek-v4-flash · 1,044 embedded
critique:    $0.0315 · 627,077 in   / 22,090 out · 31,386 cached (5%) · gpt-5.6-luna, deepseek/deepseek-v4-flash
security:    $0.0158 · 339,228 in   / 7,869 out  · 10,718 cached (3%) · gpt-5.6-luna
tests:       $0.0041 · 30,739 in    / 8,046 out  · 1,792 cached (6%)  · deepseek/deepseek-v4-flash
description: $0.0005 · 21,898 in    / 3,904 out  · 0 cached (0%)      · deepseek-v4-flash

Comment thread crates/tinyagents-harness/src/media/image_tool.rs Outdated
Comment thread crates/tinyagents-harness/src/media/test.rs Outdated
Comment thread crates/tinyagents-harness/src/media/test.rs
Comment thread crates/tinyagents-harness/src/media/video_tool.rs Outdated
Comment thread crates/tinyagents-harness/src/media/image_tool.rs
Comment thread crates/tinyagents-harness/src/media/types.rs
Comment thread crates/tinyagents-harness/src/media/image_tool.rs Outdated
Comment thread crates/tinyagents-harness/src/media/types.rs Outdated
Comment thread docs/modules/harness/media.md Outdated
@tinysweeper tinysweeper Bot added the priority: p1 Next. Wrong behaviour a user will hit, or a security weakness behind a condition. label Sep 24, 2026
When deserializing media types from JSON, the code now correctly handles optional fields that may be absent from the input, preventing deserialization failures for valid payloads that omit these fields.

Auto-committed-on: macbook
When deserializing media types from JSON, the code now correctly handles optional fields that may be absent from the input, preventing deserialization failures for valid payloads that omit non-required attributes.

Auto-committed-on: macbook
When deserializing media types from JSON, the code now correctly handles optional fields that may be absent from the input, preventing deserialization failures for valid media objects that omit non-required properties.

Auto-committed-on: macbook
When the image tool is called with a file path that does not exist, it now returns a descriptive error instead of panicking or silently failing. This improves robustness and debuggability for users providing invalid image paths.

Auto-committed-on: macbook
When the image tool returns a result without a file path, the system now gracefully handles the missing value instead of panicking. This ensures robustness when the underlying image generation or retrieval process fails to produce a valid file.

Auto-committed-on: macbook
The video_tool.rs file was missing from the tinyagents-harness crate, causing compilation failures when the media module was enabled. This change adds the file to restore the video tool functionality.

Auto-committed-on: macbook
senamakel and others added 3 commits September 24, 2026 07:52
…configs

Strengthen the workspace confinement logic so that symlinks targeting files outside the workspace are rejected, and tilde-prefixed references are treated as literal workspace paths rather than home-directory expansions. Also add validation to reject artifact subdirectories that escape the output root and integer values that exceed provider ranges, ensuring these errors are caught before billing occurs.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
… video responses

Replaces the MockVideoGenerator in the empty_video_delivery test with a dedicated EmptyVideoGenerator that returns a VideoResponse with an empty videos list. This ensures the harness correctly treats empty video deliveries as billed, non-retryable errors, and tightens the assertion to check for the "do not retry" message and the request identifier.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bc57d93dc3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/tinyagents-harness/src/media/types.rs Outdated
…for writes

The artifact directory creation now inspects every path component without following symlinks, preventing a pre-existing symlink from redirecting output outside the workspace. The persist method uses O_NOFOLLOW on Unix and create_new to refuse writing through a final-path symlink or overwriting an existing file, ensuring that a scoped workspace cannot be escaped even after the directory check.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ff76e73f6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/tinyagents-harness/src/media/types.rs
Comment thread crates/tinyagents-harness/src/media/video_tool.rs Outdated
senamakel and others added 6 commits September 24, 2026 10:41
Update the tinyinference and tinytools submodule pointers to their latest commits, pulling in recent upstream changes.

Auto-committed-on: macbook
Replace the previous O_NOFOLLOW-based file creation with an `openat` approach that holds an open file descriptor to the verified output directory. This prevents time-of-check/time-of-use races where an attacker could swap an ancestor directory after validation but before the write, redirecting the artifact to an unintended location. The `ArtifactDirectory` struct now carries the directory handle on Unix, and `persist` uses `openat` with `O_DIRECTORY | O_NOFOLLOW` to create files relative to that handle. Additionally, the video tool now derives the file extension from the media type header, supporting mp4, webm, and mov formats instead of hardcoding mp4.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a test that verifies the video tool saves media with the extension matching the content type from the provider, and add a WebmVideoGenerator stub to produce non-MP4 deliveries. The change also includes minor formatting adjustments in the media output module and adds the libc dependency to the lockfile.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…ions

Replace direct libc system calls with the rustix crate to improve safety and portability. The change uses rustix's typed file descriptors and open flags instead of raw libc constants and unsafe blocks, reducing the risk of undefined behavior while maintaining the same security properties for artifact directory and file operations.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Update the pinned commits for the tinyinference and tinytools submodules to incorporate upstream fixes and improvements.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The non-unix file creation and write operations were not scoped within a block, causing the `file` variable to be used outside its intended scope. This change wraps them in a block to ensure proper scoping and correct control flow.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0a8a55dd40

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/tinyagents-harness/src/media/types.rs
Comment thread crates/tinyagents-harness/src/media/types.rs
Comment thread crates/tinyagents-harness/src/media/video_tool.rs
Comment thread crates/tinyagents-harness/src/media/image_tool.rs

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes: 3 lane(s) blocking, worst finding is high.

Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.

             $0.0484 · 738,337 in / 54,741 out · 83,720 cached (11%) · ladder/vectors, gpt-5.6-luna, deepseek/deepseek-v4-flash, deepseek-v4-flash · 1,069 embedded
critique:    $0.0282 · 408,153 in / 25,798 out · 37,185 cached (9%)  · gpt-5.6-luna, deepseek/deepseek-v4-flash
security:    $0.0147 · 236,279 in / 16,053 out · 17,863 cached (8%)  · gpt-5.6-luna
tests:       $0.0010 · 36,823 in  / 5,549 out  · 1,024 cached (3%)   · deepseek-v4-flash
description: $0.0012 · 27,880 in  / 3,827 out  · 27,648 cached (99%) · deepseek/deepseek-v4-flash

Comment thread crates/tinyagents-harness/src/media/types.rs
Comment thread crates/tinyagents-harness/src/media/test.rs Outdated
Comment thread crates/tinyagents-harness/src/media/types.rs
Comment thread crates/tinyagents-harness/src/media/types.rs
Comment thread crates/tinyagents-harness/src/media/video_tool.rs
Comment thread crates/tinyagents-harness/src/media/video_tool.rs Outdated
Comment thread crates/tinyagents-harness/src/media/types.rs
Comment thread crates/tinyagents-harness/src/media/test.rs
Comment thread crates/tinyagents-harness/src/media/types.rs
Comment thread crates/tinyagents-harness/src/media/types.rs

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes: 2 lane(s) blocking, worst finding is high.

Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.

             $0.0817 · 1,306,915 in / 65,464 out · 64,888 cached (5%) · ladder/vectors, gpt-5.6-luna, deepseek/deepseek-v4-flash · 1,069 embedded
critique:    $0.0442 · 710,247 in   / 35,176 out · 34,524 cached (5%) · gpt-5.6-luna, deepseek/deepseek-v4-flash
security:    $0.0314 · 536,787 in   / 26,240 out · 30,364 cached (6%) · gpt-5.6-luna
description: $0.0028 · 30,896 in    / 624 out    · 0 cached (0%)      · deepseek/deepseek-v4-flash

Comment thread crates/tinyagents-harness/src/error.rs
Comment thread crates/tinyagents-harness/src/media/video_tool.rs
Comment thread crates/tinyagents-harness/src/media/types.rs
Comment thread crates/tinyagents-harness/src/media/image_tool.rs
Comment thread crates/tinyagents-harness/src/media/image_tool.rs
Comment thread crates/tinyagents-harness/src/media/video_tool.rs
Comment thread crates/tinyagents-harness/src/media/video_tool.rs
Comment thread crates/tinyagents-harness/src/media/types.rs
Comment thread crates/tinyagents-harness/src/media/types.rs
Comment thread crates/tinyagents-harness/src/media/types.rs
@tinysweeper tinysweeper Bot added priority: p0 Drop what you are doing. Data loss, a live break, or an exploitable hole. and removed priority: p1 Next. Wrong behaviour a user will hit, or a security weakness behind a condition. labels Sep 24, 2026
…r resume

Add input validation for string and string-list arguments in image and video tools to reject non-string values early. Also require the `model` parameter when resuming a video job, so the resumed result retains its submitted model. Additionally, validate that artifact name components are non-empty and safe filename components, and reject empty subdirectories in `MediaOutput::dir`.

Auto-committed-on: dragonfly
…sertions

The video tool's timeout policy now returns the configured wait timeout in milliseconds instead of being unbounded, ensuring the harness respects the tool's own deadline. The test for billed non-delivery was updated to match the actual error message, and the timeout policy assertion now expects the correct 5-second value.

Auto-committed-on: dragonfly

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2df8633745

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/tinyagents-harness/src/media/video_tool.rs Outdated
…ob handling

The video tool's timeout policy was changed from a fixed millisecond duration to unbounded, allowing the provider's own wait budget to control job polling without interference from a generic harness timeout. This ensures that when a video job is still in progress, the tool returns a resumable timeout containing the job ID rather than a generic timeout that would lose the job context.

Auto-committed-on: dragonfly
The `text_response` helper now constructs an `AssistantMessage` with an explicit `ContentBlock::Text` instead of relying on the deprecated `AssistantMessage::text` constructor. This aligns the test with the updated message API and ensures the helper continues to work after the old shorthand is removed.

Auto-committed-on: dragonfly
@senamakel
senamakel merged commit e146d13 into main Sep 24, 2026
8 of 9 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6ac4ff222e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +165 to +168
let handle = open(
&canonical_dir,
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC,
Mode::empty(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Open the artifact directory without following ancestors

When another process can mutate a workspace containing a nested output subdirectory such as a/b, it can replace a with a symlink after canonical_dir is checked but before this pathname-based open. Fresh evidence in the final tree is that O_NOFOLLOW protects only the final b component, so the retained handle itself can point outside the workspace and the later handle-relative artifact write still escapes confinement. Walk from a retained root handle and open each component without following symlinks.

Useful? React with 👍 / 👎.

Comment on lines +372 to +374
match args.get(*key) {
None | Some(Value::Null) | Some(Value::String(_)) => {}
Some(other) => return Err(format!("`{key}` must be a string, got {other}")),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject blank string options before billing

When a schema-valid call supplies a blank value such as "model": " ", this branch accepts it, but arg_str subsequently trims and discards it, so the image or video request is submitted and billed using the generator's default model rather than reporting invalid arguments. Require nonempty trimmed values for options consumed through arg_str, and cover this through the agent-loop invocation path.

AGENTS.md reference: AGENTS.md:L66-L70

Useful? React with 👍 / 👎.

Comment on lines +383 to +384
match args.get(*key) {
None | Some(Value::Null) | Some(Value::String(_)) => {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject blank scalar references before generation

When references is supplied as an empty or whitespace-only string, the model-facing schema accepts it and this branch declares it valid, while arg_list silently drops it. A reference-based edit can therefore become a separately billed text-only generation instead of failing before the provider call; apply the same nonempty check already used for array entries and add invocation coverage for the scalar form.

AGENTS.md reference: AGENTS.md:L66-L70

Useful? React with 👍 / 👎.

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes: 2 lane(s) blocking, worst finding is high.

Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.

             $0.0667 · 1,046,630 in / 49,208 out · 56,047 cached (5%) · ladder/vectors, gpt-5.6-luna, deepseek/deepseek-v4-flash · 1,081 embedded
critique:    $0.0339 · 524,521 in   / 24,804 out · 29,470 cached (6%) · gpt-5.6-luna, deepseek/deepseek-v4-flash
security:    $0.0262 · 456,935 in   / 19,938 out · 26,577 cached (6%) · gpt-5.6-luna
description: $0.0032 · 33,579 in    / 1,480 out  · 0 cached (0%)      · deepseek/deepseek-v4-flash

"inputImages",
],
) {
match self.output.reference(&raw, workspace) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high critique likely

Keep reference validation atomic with the provider read

The reference is validated and converted into a path before generate is called, so a local path can be swapped after validation (for example by replacing a symlink) and the provider can read a different target, including one outside the workspace. The validation result must bind the authorized bytes or otherwise keep the checked file identity/path invariant through the provider read; canonicalizing only during this call is not sufficient.

[RULE] atomic-path-validation ·

)];
for (index, image) in response.images.iter().enumerate() {
// Preserve the generated image format in the artifact extension
let ext = match image.media_type.split(';').next().map(str::trim) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high critique likely

Allow only safe image extensions before persisting

The extension is selected from a small allowlist, which is safer than using the provider's raw media type, but the code still persists every response image before verifying that the response's data is non-empty or actually matches the declared format. A provider response with a supported type but invalid or empty bytes can create misleading artifacts and may fail after a billed call without a clear per-image validation. Validate the image payload and media-type contract before persistence, or make the persistence layer perform that validation.

[RULE] safe-artifact-extension ·

&self.description
}

fn parameters_schema(&self) -> Value {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Add focused tests for image-tool boundary behavior

This change adds billing-sensitive behavior and runtime enforcement for empty responses, maximum image counts, malformed options, reference validation, and media-type handling, but the diff adds no tests. The repository rules require tests for every behavior change; add focused coverage for these branches so regressions cannot silently re-enable billed retries or unsafe persistence.

[RULE] missing-boundary-tests ·


// Reject empty responses as billed failures, since the call was billed
// but produced nothing to save.
if response.images.is_empty() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Add focused tests for image-tool boundary behavior

This change adds billing-sensitive behavior and runtime input enforcement, including empty responses, maximum image counts, malformed options, reference validation, and media-type handling, but the diff adds no tests. The repository rules require tests for every behavior change; add focused tests covering these branches so regressions cannot silently re-enable billed retries or unsafe persistence.

[RULE] missing-boundary-tests ·

pub(crate) fn check_string_options(args: &Value, keys: &[&str]) -> Result<(), String> {
for key in keys {
match args.get(*key) {
None | Some(Value::Null) | Some(Value::String(_)) => {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Reject empty string list arguments

This accepts an empty or whitespace-only single-string list value, such as {"references": ""}. arg_list then drops that value and the request proceeds as though the option was absent, which can silently change the requested operation and allow a billed request to run with no references. Apply the same non-empty check used for array entries to the single-string branch, or reject blank strings here.


Additional security observation

priority medium confident

Reject empty string list values

[RULE] empty-input-validation

The single-string branch accepts an empty or whitespace-only string even though this helper promises that every list entry is a non-empty string. arg_list then silently drops that value, so a malformed references argument can be treated as absent and the media request may proceed and be billed instead of failing closed. Require the string to contain non-whitespace content so empty values reach the existing error branch.

Suggested change for this observation (reference only)

None | Some(Value::Null) => {}
            Some(Value::String(text)) if !text.trim().is_empty() => {}

[RULE] silent-invalid-input ·

let mut request = ImageRequest::new(prompt);
request.model = arg_str(args, &["model"]).map(str::to_owned);
// Enforce the maximum image count at runtime
if let Some(n) = arg_u64(args, &["n", "count"]) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique likely

Reject image counts that cannot fit the request field

The runtime check only compares against MAX_IMAGES_PER_REQUEST. If that constant is larger than u32::MAX, or if the request field's representable range is narrower than the policy constant, u32::try_from(n).ok() silently turns an accepted value into None, causing the provider request to fall back to its default count. The conversion must be validated as part of the rejection condition rather than discarded.

[RULE] runtime-count-bound ·

// comparison. If the root is a fallback path that doesn't exist,
// fall back to a lexical check after canonicalizing the path.
let canonical_root = root.canonicalize().unwrap_or_else(|_| root.to_path_buf());
let admitted = match &self.reference_policy {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high security confident

Keep reference validation atomic with the provider read

The path is canonicalized and admitted here, but only a path is returned; the provider reads that path later. A concurrent replacement of the file or an ancestor can therefore redirect the provider read after validation, including through a symlink changed after canonicalize. Validate and open the reference atomically, or pass an already-open safe handle through the provider boundary.

[RULE] toctou-path-validation ·

],
) {
match self.output.reference(&raw, workspace) {
Ok(reference) => request.references.push(reference),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high security confident

Keep reference validation atomic with the provider read

The reference is canonicalized and checked before being stored in the request, but the provider reads that path later inside generate. An attacker who can modify workspace filesystem entries can replace the validated file or introduce a symlink after validation and cause the provider to read outside the workspace. Pass an opened, immutable handle or the validated file bytes to the provider, or otherwise make validation and consumption atomic.

[RULE] filesystem-toctou ·

extension: &str,
bytes: &[u8],
) -> Result<PathBuf, String> {
if !is_filename_component(stem) || !is_filename_component(extension) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high security likely

Allow only safe image extensions before persisting

This check prevents traversal but accepts any single filename component as an extension, including active or browser-interpreted formats such as html, svg, or svg+xml. If artifacts are later served or opened by clients, generated bytes can become stored content with an executable or script-capable type. Restrict extensions at the image/video call boundary to an explicit allowlist of safe provider media formats before writing the artifact.

[RULE] unsafe-artifact-extension ·

}
#[cfg(not(unix))]
{
let mut file = File::options()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium security confident

Anchor non-Unix artifact writes to the verified directory

On non-Unix targets, validation and creation use the path again after the directory check. An attacker can replace the verified directory or an ancestor with a symlink between those operations, causing generated bytes to be written outside the output root. Use a platform-specific directory-handle-relative, no-follow creation primitive, or otherwise revalidate atomically before writing.

[RULE] path-race ·

@tinysweeper tinysweeper Bot added priority: p1 Next. Wrong behaviour a user will hit, or a security weakness behind a condition. and removed priority: p0 Drop what you are doing. Data loss, a live break, or an exploitable hole. labels Sep 24, 2026

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes: 2 lane(s) blocking, worst finding is high.

Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.

          $0.0846 · 1,373,858 in / 64,255 out · 86,592 cached (6%) · ladder/vectors, gpt-5.6-luna, deepseek/deepseek-v4-flash · 1,081 embedded
critique: $0.0522 · 853,753 in   / 35,129 out · 58,010 cached (7%) · gpt-5.6-luna, deepseek/deepseek-v4-flash
security: $0.0290 · 488,654 in   / 25,862 out · 28,582 cached (6%) · gpt-5.6-luna

"inputImages",
],
) {
match self.output.reference(&raw, workspace) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority high critique likely

Keep reference validation atomic with provider reads

A local reference is validated and canonicalized here, but the resulting reference is then stored in request and read later by the provider. If an attacker can modify the workspace between these operations, they can replace a validated symlink or path component and make the provider read a file outside the workspace. Resolve the file into provider-owned bytes or otherwise hold an opened, validated handle through the provider read; canonicalizing only during validation does not close this race.

[RULE] toctou-path-validation ·

pub(crate) fn check_string_lists(args: &Value, keys: &[&str]) -> Result<(), String> {
for key in keys {
match args.get(*key) {
None | Some(Value::Null) | Some(Value::String(_)) => {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Reject empty string list values

check_string_lists accepts both an empty scalar string and an empty array: the scalar matches Value::String(_), and all(...) is true for an empty array. arg_list then drops these values, so {"references":""} and {"references":[]} are silently treated as if the option were absent. Reject blank scalar strings and empty arrays whenever the option is present.

[RULE] empty-input-validation ·

@@ -0,0 +1,74 @@
# Media generation tools

Feature: `media` on `tinyagents-harness`. Module: `tinyagents_harness::media`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Name the exposed harness feature correctly

The harness exposes multimodal, not media, as its Cargo feature. A user following this documentation cannot enable the module with --features media; the documented feature must be changed to multimodal (and the module remains tinyagents_harness::media).

Suggested change
Feature: `media` on `tinyagents-harness`. Module: `tinyagents_harness::media`.
Feature: `multimodal` on `tinyagents-harness`. Module: `tinyagents_harness::media`.

[RULE] invalid-feature-name ·

}

#[tokio::test]
async fn malformed_string_options_are_rejected_not_ignored() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Add direct coverage for image-tool boundary safeguards

This test file still does not exercise several externally visible image-tool safeguards through execute: the maximum image-count limit, unsupported response media types before persistence, non-string reference entries, or the direct reference/path rejection cases beyond traversal and symlinks. These checks are specifically billing- and filesystem-safety-sensitive, and schema assertions cannot replace runtime coverage because callers can invoke the tool directly. Add focused direct-execution tests for those branches before merging.

[RULE] missing-tests ·

})
}

/// A unique, filesystem-safe artifact stem: `<kind>-<unix-millis>-<counter>`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Make artifact stems unique across processes

The counter only provides uniqueness within one process, and the timestamp has millisecond resolution. Two processes generating the same kind in the same shared workspace during the same millisecond both produce the same stem because each process starts its counter at zero. If those artifacts are persisted under that stem, one generation can overwrite the other. Use a process-independent unique identifier (for example, a UUID or sufficiently random component) instead of relying on the process-local counter.

[RULE] artifact-collision ·

ToolTimeout::Millis(IMAGE_TIMEOUT_MS)
}

async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium security confident

Add focused tests for image-tool boundary behavior

This change adds billing-sensitive and filesystem-sensitive guards, including maximum-count enforcement, empty billed responses, malformed options, media-type rejection, and reference confinement. The image tool's direct execute path bypasses model schema validation, but this diff adds no focused tests covering those boundaries or symlink replacement behavior. Add unit tests that exercise direct execution and assert requests are rejected before billing and that unsafe references cannot escape the workspace.

[RULE] missing-boundary-tests ·

let result = tool.execute(json!({ "prompt": "x" })).await.unwrap();
assert!(result.is_error);
let message = text(&result);
assert!(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium security confident

Match the complete billed non-delivery error

This assertion only checks for two independent substrings, so a regression could change the request identifier, error classification, or wording while the test still passes. Assert the complete expected error text (or at least all contractually significant fields) so the billed non-retryable behavior is actually pinned.

[RULE] weak-test-assertion ·

let result = tool.execute(json!({ "prompt": "x" })).await.unwrap();
assert!(result.is_error);
let message = text(&result);
assert!(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium security confident

Match the complete resumable-timeout error

The timeout regression test accepts any message containing the job id and a generic phrase. It does not verify the provider reason, timeout classification, or the exact resume/no-resubmit contract, so a materially changed error could pass unnoticed. Tighten this assertion to the actual expected error text or structured fields.

[RULE] weak-test-assertion ·

canonical_dir.display()
));
}
#[cfg(unix)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium security confident

Anchor every verified artifact ancestor

The verified directory is opened through an absolute path after each ancestor was checked separately. A concurrent local attacker can replace an ancestor of canonical_dir with a symlink between those checks and open; NOFOLLOW only protects the final component, so the resulting directory handle can point outside the verified output root and openat will then safely write to the wrong directory. Traverse from an already-open root directory handle using directory-relative no-follow operations for every component, or otherwise bind the entire directory chain atomically.

[RULE] path-race ·

// Canonicalize the root if it exists, for accurate symlink-safe
// comparison. If the root is a fallback path that doesn't exist,
// fall back to a lexical check after canonicalizing the path.
let canonical_root = root.canonicalize().unwrap_or_else(|_| root.to_path_buf());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium security likely

Fail closed when the reference root cannot be canonicalized

When canonicalizing root fails, this uses the uncanonicalized root for the confinement check while comparing it with a canonical reference path. That makes the authorization decision dependent on a lexical, unresolved path and can disagree with the actual filesystem location, especially for unusual or changing workspace roots. Return an error when the root cannot be canonicalized instead of falling back to root.to_path_buf().

[RULE] canonical-path-validation ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p1 Next. Wrong behaviour a user will hit, or a security weakness behind a condition.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant