Commit b66037d
feat(safe-outputs): add unified upload-build-artifact safe output (#380)
* feat(safe-outputs): add unified upload-build-artifact safe output
Combines the concepts from #363 (upload-artifact, current build) and #373
(upload-build-artifact, arbitrary build) into a single tool that handles
both use cases via the ADO build attachments REST API.
Key design:
- build_id is Optional — omit to target the current pipeline run
(resolved from BUILD_BUILDID in Stage 3)
- REST API (PUT .../builds/{buildId}/attachments/{type}/{name}) for all
cases, replacing the ##vso[artifact.upload] logging command approach
- allowed-build-ids check is skipped when targeting the current build
(implicitly trusted)
- Full Stage 1 → Stage 3 file staging contract: validate, canonicalize,
bounds-check, stage file with extension preservation, re-canonicalize
and re-verify in Stage 3
Supersedes #363 and #373.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: move file read after dry-run guard and use Path::extension() for allowlist check
- Move std::fs::read after the dry_run early return to avoid reading
up to 50 MB into memory only to discard it in dry-run mode.
- Switch extension allowlist from suffix matching on the full path
to Path::extension() comparison, preventing false matches like
'catalog' matching allowed-extensions: ['log'].
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: restore cache-memory heading in safe-outputs.md
The heading was accidentally dropped when the upload-build-artifact
section was inserted, leaving the cache-memory paragraph as a stray
note under the wrong section.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: use async file read, add staged-file integrity check, clarify comments
- Switch std::fs::read to tokio::fs::read().await to avoid blocking
the tokio runtime for files up to 50 MB.
- Add a warn!() when the live staged-file size differs from the size
recorded at Stage 1, catching potential file tampering between stages.
- Add comments on is_valid_version() call sites noting intentional
reuse for artifact-name and attachment-type charset validation.
- Add comment on staged filename explaining max-length reasoning.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: make staged-file size mismatch a hard failure
A size mismatch between Stage 1 and Stage 3 indicates the staged file
was modified between stages — fail hard rather than warning and
uploading potentially tampered content. Adds a test for the integrity
check.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: add attachment-type dot guard, Stage 1 size cap, executor tests
- Add starts_with('.') guard to attachment-type validation, matching
the artifact_name check for consistency.
- Add defense-in-depth file size cap at Stage 1 (MCP) using the
default 50 MB limit to prevent staging-disk exhaustion before
Stage 3 enforces the operator's configured limit.
- Add three missing executor tests: extension allowlist rejection,
artifact-name allowlist rejection, and name-prefix application.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: add SHA-256 cross-stage integrity verification
Add SHA-256 hash recording at Stage 1 and verification at Stage 3 for
both file-staging safe outputs:
- upload-build-artifact: staged_sha256 field recorded after copying
the file, verified before uploading to ADO. Catches same-size file
swaps that the size-only check would miss.
- create-pull-request: patch_sha256 field recorded after writing the
patch, verified before applying. Field is Optional for backward
compatibility with existing NDJSON records.
Also adds sha256_hex() helper in upload_build_artifact module, reused
by both tools and the MCP handlers.
A separate issue (#381) tracks adding the full staging pattern
(including SHA-256) to upload-workitem-attachment, which currently
reads directly from source_directory without staging.
Also fixes: name-prefix length cap (50 chars), attachment-type
leading-dot guard, comment typo in MCP handler.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: make patch_sha256 a required field on CreatePrResult
Pipelines are versioned with their YAML — no need for backward
compatibility with old NDJSON records. Making the hash optional
created a security hole where a missing hash would skip verification
entirely. Now patch_sha256 is a required String and verification is
unconditional.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor: move sha256_hex to src/hash.rs, fix prefix slice panic
- Move sha256_hex() from upload_build_artifact.rs to a dedicated
src/hash.rs module, removing the coupling between create_pr,
mcp.rs and the upload_build_artifact module.
- Fix potential panic in name-prefix error path: &prefix[..20]
can panic on multi-byte UTF-8 boundaries. Use
prefix.chars().take(20).collect() instead.
- Revert upload_build_artifact module visibility back to private.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: add SHA-256 mismatch tests, fix TOCTOU, extract is_valid_artifact_name
- Add non-dry-run SHA-256 mismatch tests for both upload-build-artifact
and create-pull-request (the mismatch path fires before any HTTP call,
so no credentials are needed).
- Fix TOCTOU in MCP handler: hash the source bytes before writing to
the staging directory, instead of re-reading the staged copy.
- Extract is_valid_artifact_name() in validate.rs to decouple artifact
name validation from version string validation, preventing silent
breakage if is_valid_version is ever tightened.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor: import DEFAULT_MAX_FILE_SIZE in MCP handler
Replace the floating STAGE1_MAX_FILE_SIZE copy with a direct import
of DEFAULT_MAX_FILE_SIZE from upload_build_artifact, ensuring the
Stage 1 cap stays in sync if the default is ever changed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>1 parent 1790b9b commit b66037d
11 files changed
Lines changed: 1584 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
37 | 38 | | |
38 | 39 | | |
39 | 40 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
418 | 418 | | |
419 | 419 | | |
420 | 420 | | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
421 | 459 | | |
422 | 460 | | |
423 | 461 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
| 21 | + | |
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
| |||
91 | 92 | | |
92 | 93 | | |
93 | 94 | | |
| 95 | + | |
94 | 96 | | |
95 | 97 | | |
96 | 98 | | |
| |||
273 | 275 | | |
274 | 276 | | |
275 | 277 | | |
| 278 | + | |
276 | 279 | | |
277 | 280 | | |
278 | 281 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| |||
0 commit comments