Repository navigation
Fix orchestration file generation when /app is root-owned (V2) - #1626
Merged
Paul Lizer (paullizer) merged 6 commits intoOct 3, 2026
Merged
Paul Lizer (paullizer) merged 6 commits into
Paul Lizer (paullizer) merged 6 commits into
Conversation
Since the OneNote runtime stage (c9faeae), /app in the image is owned by root while the app runs as UID 65532 with /app as its working directory. The structured file renderer and the generated-file download stream created scratch files with tempfile.TemporaryFile(dir='.'), so every CSV, JSON, XML, YAML, TXT and Markdown render failed with PermissionError, reported as output_access_denied. - Add functions_temp_files.scratch_file_dir(): /sc-temp-files when it is writable, otherwise the platform temp directory, never the working directory. Use it in both call sites. - Dockerfile: onenote-runtime no longer creates /app. Copy the extractor binary in the final stage after the app code, so the runtime user's chowned copy creates /app. The binary stays root-owned. - Record error_type, error_cause_type and error_errno in render-attempt diagnostics, without messages or paths. - Add regression tests. Update two tests that pinned dir='.' and one source-loaded namespace. - Docs: fix document, logging tags, OneNote build layout, release notes. Bump to 0.261.218. Fixes #1623 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The target branch reached 0.261.227 and already used 0.261.218 for another fix. Move this fix's release notes entry to a new 0.261.228 section, keep the target's 0.261.218 entry, and renumber the fix document, the logging reference note and the regression test header. Refs #1623 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The target branch took 0.261.228 for the V2 workflow alert card change (#1625). Keep its 0.261.228 release notes entry, move this fix to a new 0.261.229 section, and renumber the fix document, the logging reference note and the regression test header. Refs #1623 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The target branch moved to 0.261.231 (#1627 through #1631) and now uses 0.261.229, 0.261.230 and 0.261.231. Keep those release notes entries, move this fix to a new 0.261.232 section, and renumber the fix document, the logging reference note and the regression test header. Refs #1623 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The final stage inherited the onenote-runtime build stage. That stage once created /app as root, and the Malicious PR Security Review flags FROM onenote-runtime as a floating base image on any PR that touches the Dockerfile, because a stage name has no tag or digest. After the previous commit the stage held only the OneNote extractor's notices, licenses and source. Remove it, build the final stage FROM mcr.microsoft.com/azurelinux/distroless/python:3.12 as before c9faeae, and copy those files from onenote-builder at the top of the final stage. The image contents and paths are unchanged, and the checker is not modified. - The scratch-dir regression test now requires that the final stage does not inherit any build stage and that its base is pinned. - test_v2_onenote_uploads requires the distroless base and the extractor binary, notices, licenses and source in the final stage itself. - Update the OneNote build layout and the fix document. Refs #1623 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The target branch took 0.261.232 for #1632, where saved chat, Analyze and orchestration results take their container's access. Keep its release notes entry, move this fix to a new 0.261.233 section, and renumber the fix document, the logging reference note and the test headers. #1632 also changed what output_access_denied means: its message now says access to the file's conversation could not be confirmed, and a changed source no longer fails a retained file. The PermissionError classification is unchanged. Reword this fix's notes, which called the code a source-access refusal, to call it an access refusal. Refs #1623 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer)
merged commit Oct 3, 2026
f7e268c
into
paullizer-react-v2-ui
11 checks passed
8 of 9 tasks
Paul Lizer (paullizer)
added a commit
to paullizer/simplechat
that referenced
this pull request
Oct 3, 2026
The base now ships its own v0.261.233 release-notes section (microsoft#1626, microsoft#1633), so this branch's entry moves to a new v0.261.234 section above it. The base section is kept verbatim. config.py merged cleanly because both sides set 0.261.233; the renumbering follows in the next commit. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This was referenced Oct 3, 2026
Paul Lizer (paullizer)
added a commit
to paullizer/simplechat
that referenced
this pull request
Oct 6, 2026
While this branch was being synced, paullizer-react-v2-ui merged microsoft#1626 and microsoft#1633 and published v0.261.233, which this branch had just taken for its first merge phase. The integration branch's sections are kept exactly as published. This branch's eight sections shift up one, to v0.261.234 through v0.261.241, and config.py becomes 0.261.241. Every file only this branch changed is checked to equal its previous content with those versions shifted, and the release notes are the integration branch's file plus this branch's eight sections. functions_structured_file_renderers.py conflicted on imports only: the exact-tabular profiles import from this branch and the scratch directory import from the integration branch are both kept. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer)
added a commit
that referenced
this pull request
Oct 6, 2026
Brings in #1626 (orchestration file render permission fix, released as 0.261.233) and #1633 (roadmap updates). The only conflict was the top of release_notes.md, where both sides added a v0.261.233 section; both are kept intact under their own headings. 6b-2 is renumbered to 0.261.234 in the next commit. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer)
added a commit
that referenced
this pull request
Oct 6, 2026
#1626 released 0.261.233 first, so 6b-2 moves to the next patch version: config.py, its release notes heading, and every 6b-2 version reference in its feature docs, test headers and version assertions. #1626's own 0.261.233 references are unchanged. Refs #1546 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
output_access_denied("This file could not be created."). Downloading any orchestration-generated file, Word and PDF included, used the same failing step./appin the image is owned by root, while the app runs as UID 65532 with/appas its working directory. The renderer and the verified download stream created scratch files withtempfile.TemporaryFile(dir='.'), so the OS refused them.output_failure()reports anyPermissionErroras an access refusal (output_access_denied), which hid the real cause. Confirmed from the deployed image layers: before c9faeae,/appwas owned by65532and a CSV render completed. After it,/appis owned by0:0.functions_temp_files.scratch_file_dir()returns/sc-temp-fileswhen it's writable, otherwise the platform temp directory, and never the working directory. Both call sites use it. In the Dockerfile, theonenote-runtimestage is removed and the final stage builds from the pinnedmcr.microsoft.com/azurelinux/distroless/python:3.12image again, as it did before c9faeae. The extractor's notices, licenses, and source are copied at the top of the final stage. The extractor binary is copied after the app code, so/appis created by the runtime user's chowned copy. The binary stays root-owned.[ORCHESTRATION_EXECUTOR] A file render attempt finished.event now logssc_error_type,sc_error_cause_type, andsc_error_errno: class names and errno only, never messages or paths. The user-facing classification is unchanged.Review notes:
dir='.'("must stay in the project"), which pinned this defect. They now assert thescratch_file_dir()location. A sandbox whose system temp directory isn't writable should setTMPDIR/TEMPinstead.FROM onenote-runtimebecause a build-stage name has no tag or digest. Removing the stage clears that false positive without changing the checker. The check passes on this PR, and run locally against the current base it reports no blocker findings.functional_tests/test_v2_onenote_uploads.pypreviously required the final stage to inheritonenote-runtime. It now requires the distroless base and the extractor binary, notices, licenses, and source in the final stage itself. The image contents and paths are unchanged.output_access_deniedto mean access to the file's conversation could not be confirmed, and a changed source no longer fails a retained file. ThePermissionErrorclassification is unchanged, so a file-system error still shares that code. The new diagnostics tell the two apart, and this PR's notes call it an access refusal rather than a source-access refusal.paullizer-react-v2-uiand renumbered to 0.261.233, because the target had already used 0.261.218 and 0.261.228 through 0.261.232.Linked issue
Fixes #1623
Release Notes & Latest Features
Is this visible to end users?
Is this admin-facing (Admin Settings, governance, deployment, config)?
Should this become a Latest Feature card?
Screenshot needed for the card?
Version bump
application/single_app/config.pyVERSIONthird segment bumped, or not needed because this is docs-only (0.261.232 -> 0.261.233)deployers/version.txtbumped, or not needed becausedeployers/was not changedTesting / validation
$env:PYTHONPATH = "$PWD\application\single_app;$PWD\functional_tests"; python -m pytest -q -p no:cacheprovider <file>. All passed:functional_tests/test_orchestration_file_render_scratch_dir.py(new): 6 passedfunctional_tests/test_generated_file_structured_renderers.py: 300 passedfunctional_tests/test_orchestration_output_lifecycle.py: 218 passedfunctional_tests/test_orchestration_output_cleanup.py: 72 passedfunctional_tests/test_workflow_saved_output_artifacts.py: 42 passedfunctional_tests/test_orchestration_output_parent_claim.py: 30 passedfunctional_tests/test_orchestration_output_history_routes.py: 25 passedfunctional_tests/test_saved_results_container_access_chat_orchestration.py: 23 passed, 6 subtests passedfunctional_tests/test_document_provenance_publication.py: 18 passedfunctional_tests/test_orchestration_render_check_cadence.py: 15 passedfunctional_tests/test_orchestration_artifact_publication.py: 13 passedfunctional_tests/test_v2_onenote_uploads.py: 8 passed, 13 subtests passeddir='.', the new end-to-end test fails with afailedrender, as production did, and the static guard reports both lines. Both pass with the fix.FROM onenote-runtime), and pass on the new one.python scripts/check_malicious_pr_security_review.py --base-sha <base> --head-sha HEADagainst the current base: no blocker findings. Run directly, its Dockerfile rule finds 1 blocker in the base's Dockerfile and 0 in this PR's.HEADcheckout. Causes include stale fixtures, a local werkzeug version mismatch, existing docs link and logging-tag findings, and release-notes pages that were already out of date. The remaining file wastest_workflow_saved_output_artifacts.py; I fixed it by passingscratch_file_dirinto the namespace it loads the function into. Separately,test_tabular_row_orchestration_scale.pytimed out after 45 minutes on bothHEADand this branch.pyOpenSSLupgraded. The globalpyOpenSSL 25.3.0doesn't work withcryptography 50.0.0.git diff --checkandpython -m py_compileon the changed Python files: clean.output_access_deniedafter about 261 ms, with 1 full check, 1 light check, and 1 source recheck. I also inspected/appownership in the deployed image layers in ACR./appis owned by65532, that the OneNote notices exist under/usr/share/simplechat/onenote-extractor/, and that an orchestration CSV completes and downloads.Documentation
ONENOTE_INGESTION.mdbuild layout,docs/reference/logging-tags.mdrender-attempt fields)docs/explanation/fixes/ORCHESTRATION_FILE_RENDER_PERMISSION_FIX.md)Security checklist
@swagger_route(security=get_auth_security())(N/A: no routes added or changed)sanitize_settings_for_user()(N/A: no settings sent to the frontend)