Skip to content

feat: add edit-wiki-page safe output#58

Merged
jamesadevine merged 5 commits intomainfrom
copilot/add-edit-wiki-page-safeoutput
Mar 19, 2026
Merged

feat: add edit-wiki-page safe output#58
jamesadevine merged 5 commits intomainfrom
copilot/add-edit-wiki-page-safeoutput

Conversation

Copy link
Contributor

Copilot AI commented Mar 19, 2026

Adds edit-wiki-page as a new safe output, enabling agents to update existing Azure DevOps wiki pages through the standard two-stage safe output pipeline. Page creation is intentionally out of scope — a separate safe output will handle that.

Agent parameters

  • path — wiki page path (e.g. /Overview/Architecture); must not contain .. or null bytes; page must already exist
  • content — markdown content (≥10 chars)
  • comment — optional commit message

Front-matter configuration

safe-outputs:
  edit-wiki-page:
    wiki-name: "MyProject.wiki"          # required
    wiki-project: "OtherProject"         # optional; defaults to pipeline project
    path-prefix: "/agent-output"         # restrict writes to this subtree
    title-prefix: "[Agent] "             # prepended to the last path segment
    comment: "Updated by agent"          # default commit message

Implementation

  • Stage 1 (MCP): validates path (no .., no null bytes, non-empty) and content length; strips control chars from path; applies full sanitize_text() to content and comment before writing to safe_outputs.ndjson
  • Stage 2 (executor): GET page to check existence and retrieve ETag → fail immediately if page does not exist (404) → enforce path-prefix → apply title-prefix → PUT to ADO Wiki REST API 7.0 with If-Match for optimistic concurrency
  • Compiler: edit-wiki-page added to WRITE_REQUIRING_SAFE_OUTPUTS — compilation fails without a permissions.write service connection
  • Security: path-prefix restriction prevents writes outside authorized subtrees; belt-and-suspenders .. check in both Stage 1 and Stage 2; PATH_SEGMENT constant shared via src/tools/mod.rs to percent-encode project and wiki_name in URL construction

[!WARNING]

Original prompt

Let's draft an edit-wiki-page safeoutput for ado-aw.

Look at how other safe outputs are implemented in the code base.

The frontmatter should allow the user to configure the wiki name, path where files can be written, prepended text for each page title added, anything else that would be good to include based on the capabilities of the azure devops api.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Implements a new `edit-wiki-page` safe output that allows agents to
create or update Azure DevOps wiki pages.

Stage 1 (MCP server):
- `edit-wiki-page` tool registered on the SafeOutputs MCP server
- Validates path (no `..`, non-empty), content (≥10 chars)
- Strips control characters from path; full sanitize_text() on content/comment
- Queues result to safe_outputs.ndjson

Stage 2 (executor):
- Dispatches `edit-wiki-page` to EditWikiPageResult::execute_sanitized()
- GET wiki page to check existence and obtain ETag
- PUT to create/update with If-Match header for optimistic concurrency
- Enforces path-prefix restriction configured in front matter
- Applies title-prefix to final path segment if configured

Front-matter configuration (safe-outputs.edit-wiki-page):
- wiki-name: wiki identifier (required)
- wiki-project: ADO project override (optional)
- path-prefix: restrict writes to pages under this path (optional)
- title-prefix: prepend text to each page title (optional)
- comment: default commit message (optional)
- create-if-missing: allow creating new pages (default: true)

Compiler:
- edit-wiki-page added to WRITE_REQUIRING_SAFE_OUTPUTS so compilation
  fails if no permissions.write service connection is configured

Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Copilot AI changed the title [WIP] Add edit wiki page safeoutput for ado-aw feat: add edit-wiki-page safe output Mar 19, 2026
Copilot AI requested a review from jamesadevine March 19, 2026 11:39
@jamesadevine
Copy link
Collaborator

@copilot implement the executor step too. Align with other executor implementations that use the devops api

Copy link
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot implement the executor step too. Align with other executor implementations that use the devops api

The Stage 2 executor is already implemented in commit 4ccf0b2 inside src/tools/edit_wiki_page.rs (execute_impl on EditWikiPageResult). It follows the same ADO API patterns as create_work_item.rs and create_pr.rs:

  • Uses reqwest::Client::new() + basic_auth("", Some(token))
  • URL format {org_url.trim_end_matches('/')}/{project}/_apis/wiki/wikis/{wiki_name}/pages (same as create_work_item.rs)
  • GET the page first to check existence and capture the ETag from the response header
  • PUT with If-Match: {ETag} for optimistic concurrency on updates; no If-Match when creating a new page
  • Returns ExecutionResult::success_with_data with page id, url, wiki, project, and action (created/updated)
  • Returns ExecutionResult::failure with the HTTP status and error body on any API error

@jamesadevine
Copy link
Collaborator

/rust-review

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

Rust PR Reviewer completed successfully!

@github-actions
Copy link
Contributor

🔍 Rust PR Review

Summary: Looks good overall — solid new safe-output tool with good test coverage, but has two correctness issues worth addressing.

Findings

🐛 Bugs / Logic Issues

  • src/tools/edit_wiki_page.rs:35-39.. check is a substring match, not a path-component check

    ensure!(!self.path.contains(".."), "path must not contain '..'");

    This rejects legitimate wiki paths like /Release-Notes/v1.0..1 or /Topic/Foo..Bar. The check should be component-aware:

    ensure!(
        !self.path.split('/').any(|seg| seg == ".."),
        "path must not contain '..' as a path component"
    );

    The same substring check exists in execute_impl at line ~212 as a belt-and-suspenders guard — both should be updated.

  • src/tools/edit_wiki_page.rs:239-244wiki_name (and project) are interpolated raw into the URL path

    let base_url = format!(
        "{}/{}/_apis/wiki/wikis/{}/pages",
        org_url.trim_end_matches('/'),
        project,
        wiki_name,  // <-- no encoding
    );

    If wiki_name contains # (fragment delimiter) or ? (query start), reqwest will parse those characters structurally rather than encoding them, silently corrupting the URL. Spaces are handled (the url crate encodes them), but #/? are not. Since wiki_name comes from operator-controlled front matter this is low severity, but a percent_encoding::utf8_percent_encode(wiki_name, percent_encoding::NON_ALPHANUMERIC) (or the more permissive PATH_SEGMENT) encode would harden it. Note: project has the same pattern and this matches the existing style in create_work_item.rs, so fixing both consistently would be ideal.

✅ What Looks Good

  • Defense-in-depth sanitization model is solid: Stage 1 (MCP handler) manually sanitizes EditWikiPageParams before try_into(), and Stage 2 re-runs sanitize_fields() via execute_sanitized() — this is intentional and correct.
  • path_prefix enforcement in execute_impl is well-designed: it normalizes both the path and the prefix before comparison, and the test_execute_path_outside_prefix_rejected unit test covers the security boundary.
  • write_safe_output_file error is silently discarded (let _ = ...) — consistent with every other tool in mcp.rs; acceptable.
  • ETag-based optimistic concurrency for the PUT is a nice touch and prevents silent data races on concurrent runs.
  • Test coverage is thorough: validation, serialization, path helpers, sanitization, and executor failure modes are all exercised.

Generated by Rust PR Reviewer for issue #58 ·

@jamesadevine
Copy link
Collaborator

@copilot src/tools/edit_wiki_page.rs:239-244 — wiki_name (and project) are interpolated raw into the URL path

let base_url = format!(
"{}/{}/_apis/wiki/wikis/{}/pages",
org_url.trim_end_matches('/'),
project,
wiki_name, // <-- no encoding
);
If wiki_name contains # (fragment delimiter) or ? (query start), reqwest will parse those characters structurally rather than encoding them, silently corrupting the URL. Spaces are handled (the url crate encodes them), but #/? are not. Since wiki_name comes from operator-controlled front matter this is low severity, but a percent_encoding::utf8_percent_encode(wiki_name, percent_encoding::NON_ALPHANUMERIC) (or the more permissive PATH_SEGMENT) encode would harden it. Note: project has the same pattern and this matches the existing style in create_work_item.rs, so fixing both consistently would be ideal.

Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot src/tools/edit_wiki_page.rs:239-244 — wiki_name (and project) are interpolated raw into the URL path

let base_url = format!(
...

Fixed in 8e1106e. Both edit_wiki_page.rs and create_work_item.rs now percent-encode the path segments (project, wiki_name, work_item_type) using percent_encoding::utf8_percent_encode with a PATH_SEGMENT set that encodes #, ?, /, and space — the structural delimiters that would silently corrupt the URL if left raw. percent-encoding 2.3 was added as an explicit Cargo.toml dependency (it was already a transitive dep via reqwest/url).

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • api.github.com (HTTP Only)
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com /lib64/ld-linux-x86-64.so.2 -pie 0e408.00mdgrb20p5x77eazk2p9gjyy.1cyioin.rcgu.o 0e408.01teq1ycpdi29xtq38m58avzw.1cyioin.rcgu.o 0e408.04wdksz2udhbsmhbmz31ifrn2.1cyioin.rcgu.o 0e408.0roi7kz8fyd01vr8wopkslw4t.1cyioin.rcgu.o 0e408.0w0s0sshyajplf2l699oxuwfk.1cyioin.rcgu.o 0e408.0zz0925kng4sqhjswpq47h7d1.1cyioin.rcgu.o 0e408.1h8m7eedzoqh87j6ntnde3cky.1cyioin.rcgu.o 0e408.21isguhevtn32dqcdl1j05fjf.1cyioin.rcgu.o 0e408.27hw7b2y70hemqls64pwyeeau.1cyioin.rcgu.o 0e408.2ap69xa5jhcu8bzvi8nf665ru.1cyioin.rcgu.o 0e408.2l7sln5w36yyxwfvup96uk3iy.1cyioin.rcgu.o 0e408.2z3o4ny3w29nexl27pg3lovk0.1cyioin.rcgu.o 0e408.31ku1szu3n6wt3joczv7xzi8i.1cyioin.rcgu.o 0e408.3q1z69vza816v6i52bhfjdqwn.1cyioin.rcgu.o (packet block)
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.1now /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.1-fuse-ld=lld /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2-z /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2relro /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2-o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2kxma�� /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2nkx7ezwyyqca5jhkfvfksmjz.0gf8kx7.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2tuk7drm704exbz4hyppemjx0.0gf8kx7.rcgu.o l2k4�� baok3emibk8wmp8dmv4.1mpvzir.rcgu.o lb4en5ayn09l2b6c20h.1mpvzir.rcgu.o f/tempfile-3.24.--crate-type s/tracing_attribcc s/tracing_attrib-m64 s/tracing_attrib/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustctIinKE/symbols.o known-linux-gnu//home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.0--eh-frame-hdr (packet block)
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com kxma�� nkx7ezwyyqca5jhkfvfksmjz.0gf8kx7.rcgu.o tuk7drm704exbz4hyppemjx0.0gf8kx7.rcgu.o e-486665cf5f9386--emit=dep-info,link e-486665cf5f9386cc e-486665cf5f9386-m64 e-486665cf5f9386/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustcblWFcx/symbols.o e-486665cf5f9386/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0139go-U0 e-48�� mbols.o c56adb783e73e6.0/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.03zwhb-plugin c56adb783e73e6.0/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.04o8qg/usr/libexec/gcc/x86_64-linux-gnu/13/liblto_plugin.so c56adb783e73e6.0git c56adb783e73e6.1show c56adb783e73e6.14ccf0b27b40a2836d4365b2a0238c62f80880939:src/tools/edit_wiki_page.rs c56adb783e73e6.1/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0gs97t--eh-frame-hdr (packet block)
  • dev.azure.com
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com --allow dev.azure.com --allow *.visualstudio.com 0e408.04wdksz2udhbsmhbmz31ifrn2.1cyioin.rcgu.o 0e408.0roi7kz8fyd01vr8wopkslw4t.1cyioin.rcgu.o 0e408.0w0s0sshyajplf2l699oxuwfk.1cyioin.rcgu.o 0e408.0zz0925kng4sqhjswpq47h7d1.1cyioin.rcgu.o 0e408.1h8m7eedzoqh87j6ntnde3cky.1cyioin.rcgu.o 0e408.21isguhevtn32dqcdl1j05fjf.1cyioin.rcgu.o 0e408.27hw7b2y70hemqls64pwyeeau.1cyioin.rcgu.o 0e408.2ap69xa5jhcu8bzvi8nf665ru.1cyioin.rcgu.o 0e408.2l7sln5w36yyxwfvup96uk3iy.1cyioin.rcgu.o 0e408.2z3o4ny3w29nexl27pg3lovk0.1cyioin.rcgu.o 0e408.31ku1szu3n6wt3joczv7xzi8i.1cyioin.rcgu.o 0e408.3q1z69vza816v6i52bhfjdqwn.1cyioin.rcgu.o (dns block)
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com --allow dev.azure.com --allow *.visualstudio.com /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83 /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2kxma�� /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2nkx7ezwyyqca5jhkfvfksmjz.0gf8kx7.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2tuk7drm704exbz4hyppemjx0.0gf8kx7.rcgu.o l2k4�� baok3emibk8wmp8dmv4.1mpvzir.rcgu.o lb4en5ayn09l2b6c20h.1mpvzir.rcgu.o mbols.o -3723d82190d9318cc -3723d82190d9318-m64 -3723d82190d9318/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustctIinKE/symbols.o -3723d82190d9318/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.0-plugin-opt=-fresolution=/tmp/ccuIoaPb.res (dns block)
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com --allow dev.azure.com --allow *.visualstudio.com e-486665cf5f9386cc e-486665cf5f9386-m64 e-486665cf5f9386/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustcblWFcx/symbols.o e-486665cf5f9386/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0139gosgqilmsx1rcmhyzc662.0mm17mr.rcgu.o e-48�� mbols.o c56adb783e73e6.0/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.03zwhb-flavor c56adb783e73e6.0/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.04o8qggnu c56adb783e73e6.0git c56adb783e73e6.1show c56adb783e73e6.14ccf0b27b40a2836d4365b2a0238c62f80880939:src/tools/edit_wiki_page.rs c56adb783e73e6.1/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0gs97t-plugin-opt=-fresolution=/tmp/ccWGD8gP.res (dns block)
  • msazuresphere.visualstudio.com
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com --allow dev.azure.com --allow *.visualstudio.com 0e408.04wdksz2udhbsmhbmz31ifrn2.1cyioin.rcgu.o 0e408.0roi7kz8fyd01vr8wopkslw4t.1cyioin.rcgu.o 0e408.0w0s0sshyajplf2l699oxuwfk.1cyioin.rcgu.o 0e408.0zz0925kng4sqhjswpq47h7d1.1cyioin.rcgu.o 0e408.1h8m7eedzoqh87j6ntnde3cky.1cyioin.rcgu.o 0e408.21isguhevtn32dqcdl1j05fjf.1cyioin.rcgu.o 0e408.27hw7b2y70hemqls64pwyeeau.1cyioin.rcgu.o 0e408.2ap69xa5jhcu8bzvi8nf665ru.1cyioin.rcgu.o 0e408.2l7sln5w36yyxwfvup96uk3iy.1cyioin.rcgu.o 0e408.2z3o4ny3w29nexl27pg3lovk0.1cyioin.rcgu.o 0e408.31ku1szu3n6wt3joczv7xzi8i.1cyioin.rcgu.o 0e408.3q1z69vza816v6i52bhfjdqwn.1cyioin.rcgu.o (dns block)
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com --allow dev.azure.com --allow *.visualstudio.com /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83 /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2kxma�� /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2nkx7ezwyyqca5jhkfvfksmjz.0gf8kx7.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2tuk7drm704exbz4hyppemjx0.0gf8kx7.rcgu.o l2k4�� baok3emibk8wmp8dmv4.1mpvzir.rcgu.o lb4en5ayn09l2b6c20h.1mpvzir.rcgu.o mbols.o -3723d82190d9318cc -3723d82190d9318-m64 -3723d82190d9318/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustctIinKE/symbols.o -3723d82190d9318/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.0-plugin-opt=-fresolution=/tmp/ccuIoaPb.res (dns block)
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com --allow dev.azure.com --allow *.visualstudio.com e-486665cf5f9386cc e-486665cf5f9386-m64 e-486665cf5f9386/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustcblWFcx/symbols.o e-486665cf5f9386/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0139gosgqilmsx1rcmhyzc662.0mm17mr.rcgu.o e-48�� mbols.o c56adb783e73e6.0/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.03zwhb-flavor c56adb783e73e6.0/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.04o8qggnu c56adb783e73e6.0git c56adb783e73e6.1show c56adb783e73e6.14ccf0b27b40a2836d4365b2a0238c62f80880939:src/tools/edit_wiki_page.rs c56adb783e73e6.1/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0gs97t-plugin-opt=-fresolution=/tmp/ccWGD8gP.res (dns block)
  • raw.github.com
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow *.github.com /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0hucix/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.29zb5tvlqb5tt29okd9ty3n8v.0gf8kx7.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0hwyty/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2gbp02zi6r6wah1ddwpeqlnpu.0gf8kx7.rcgu.o 0e408.00mdgrb20p5x77eazk2p9gjyy.1cyioin.rcgu.o 0e408.01teq1ycpdi29xtq38m58avzw.1cyioin.rcgu.o 0e408.04wdksz2udhbsmhbmz31ifrn2.1cyioin.rcgu.o 0e408.0roi7kz8fyd01vr8wopkslw4t.1cyioin.rcgu.o 0e408.0w0s0sshyajplf2l699oxuwfk.1cyioin.rcgu.o 0e408.0zz0925kng4sqhjswpq47h7d1.1cyioin.rcgu.o 0e408.1h8m7eedzoqh87j6ntnde3cky.1cyioin.rcgu.o 0e408.21isguhevtn32dqcdl1j05fjf.1cyioin.rcgu.o 0e408.27hw7b2y70hemqls64pwyeeau.1cyioin.rcgu.o 0e408.2ap69xa5jhcu8bzvi8nf665ru.1cyioin.rcgu.o 0e408.2l7sln5w36yyxwfvup96uk3iy.1cyioin.rcgu.o 0e408.2z3o4ny3w29nexl27pg3lovk0.1cyioin.rcgu.o 0e408.31ku1szu3n6wt3joczv7xzi8i.1cyioin.rcgu.o 0e408.3q1z69vza816v6i52bhfjdqwn.1cyioin.rcgu.o (dns block)
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow *.github.com /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.1/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0md5qe5dutswaws174h5iuhoq.0mm17mr.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.1/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0mosvevliifzznub1pu1ta9nd.0mm17mr.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0rdep2l2k4lgotqksrier2b0t.0mm17mr.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0stugybaok3emibk8wmp8dmv4.0mm17mr.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0zvminlb4en5ayn09l2b6c20h.0mm17mr.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2kxma�� /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2nkx7ezwyyqca5jhkfvfksmjz.0gf8kx7.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2tuk7drm704exbz4hyppemjx0.0gf8kx7.rcgu.o l2k4�� baok3emibk8wmp8dmv4.1mpvzir.rcgu.o lb4en5ayn09l2b6c20h.1mpvzir.rcgu.o known-linux-gnu/--crate-type s/tracing_attribcc s/tracing_attrib-m64 s/tracing_attrib/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustctIinKE/symbols.o 8b16cf8321/out/p/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.0/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.03zwhbw6du63m9e7k3dgsssj3.0mm17mr.rcgu.o (dns block)
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow *.github.com kxma�� nkx7ezwyyqca5jhkfvfksmjz.0gf8kx7.rcgu.o tuk7drm704exbz4hyppemjx0.0gf8kx7.rcgu.o lib/rustlib/x86_64-REDACTED-linux-gnu/bin/self-contained/cc a869db8d.paste.5cc a869db8d.paste.5-m64 a869db8d.paste.5/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustcblWFcx/symbols.o a869db8d.paste.5/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0139go-U0 a869�� mbols.o c56adb783e73e6.0/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.03zwhb-m64 c56adb783e73e6.0/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.04o8qg/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustciBCMk7/symbols.o c56adb783e73e6.0git c56adb783e73e6.1show c56adb783e73e6.14ccf0b27b40a2836d4365b2a0238c62f80880939:src/tools/edit_wiki_page.rs c56adb783e73e6.1/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.0gs97t/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-5c39ca9a324ebf83.03zwhbw6du63m9e7k3dgsssj3.1nn9ifj.rcgu.o (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@jamesadevine
Copy link
Collaborator

/rust-review

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

Rust PR Reviewer completed successfully!

@github-actions
Copy link
Contributor

🔍 Rust PR Review

Summary: Solid implementation with good security fundamentals — one weak test and one code-quality issue worth addressing before merge.


Findings

🐛 Bugs / Logic Issues

  • src/tools/edit_wiki_page.rs:725–753test_execute_create_if_missing_false_rejected is effectively a dead test. The test comment admits the intended path (actually exercising the create_if_missing guard in execute_impl) was abandoned in favour of testing config deserialization only. The guard at line 288 (if !page_exists && !config.create_if_missing) is never exercised by the test suite. If this guard regresses, no test catches it. Either mock the HTTP call or restructure execute_impl to expose a testable helper that checks the guard without making a network request.

⚠️ Suggestions

  • src/tools/edit_wiki_page.rs:18 and src/tools/create_work_item.rs:18 — duplicate PATH_SEGMENT constant. Both files define the exact same &AsciiSet. Since it was freshly introduced in create_work_item.rs as part of this PR, this is a good time to lift it to src/tools/mod.rs (or a small src/tools/url.rs utility) so future tools can share it without copying.

  • src/tools/edit_wiki_page.rs:36–52validate() doesn't check for null bytes in path. create_pr.rs (line 1040) and memory.rs (line 53–56) both call ensure!(!path.contains('\0'), ...) explicitly. Null bytes are stripped by sanitize_fields() in both stages so there's no actual exploit path here, but the explicit check keeps validate() consistent with project convention and makes intent clear.


✅ What Looks Good

  • Path-prefix enforcement correctly uses starts_with(&format!("{prefix}/")) rather than a bare starts_with(prefix), preventing /agent-output from matching /agent-outputevil.
  • Belt-and-suspenders .. check: validated in Stage 1 (validate()) and re-checked in Stage 2 (execute_impl), consistent with the rest of the codebase.
  • ETag-based optimistic concurrency: the GET→ETag→PUT flow prevents silent overwrite races on existing pages.
  • edit-wiki-page added to WRITE_REQUIRING_SAFE_OUTPUTS in common.rs — compilation fails without a permissions.write service connection, matching the security model for create-pull-request and create-work-item.
  • URL-encoding of project and wiki_name in the base URL prevents URL structure corruption for values with spaces, #, ?, or /.
  • The silent write pattern (let _ = self.write_safe_output_file(...).await) is consistent with every other MCP tool handler in mcp.rs.

Generated by Rust PR Reviewer for issue #58 ·

@jamesadevine
Copy link
Collaborator

@copilot 🐛 Bugs / Logic Issues
src/tools/edit_wiki_page.rs:725–753 — test_execute_create_if_missing_false_rejected is effectively a dead test. The test comment admits the intended path (actually exercising the create_if_missing guard in execute_impl) was abandoned in favour of testing config deserialization only. The guard at line 288 (if !page_exists && !config.create_if_missing) is never exercised by the test suite. If this guard regresses, no test catches it. Either mock the HTTP call or restructure execute_impl to expose a testable helper that checks the guard without making a network request.
⚠️ Suggestions
src/tools/edit_wiki_page.rs:18 and src/tools/create_work_item.rs:18 — duplicate PATH_SEGMENT constant. Both files define the exact same &AsciiSet. Since it was freshly introduced in create_work_item.rs as part of this PR, this is a good time to lift it to src/tools/mod.rs (or a small src/tools/url.rs utility) so future tools can share it without copying.

src/tools/edit_wiki_page.rs:36–52 — validate() doesn't check for null bytes in path. create_pr.rs (line 1040) and memory.rs (line 53–56) both call ensure!(!path.contains('\0'), ...) explicitly. Null bytes are stripped by sanitize_fields() in both stages so there's no actual exploit path here, but the explicit check keeps validate() consistent with project convention and makes intent clear.

Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot 🐛 Bugs / Logic Issues
src/tools/edit_wiki_page.rs:725–753 — test_execute_create_if_missing_false_rejected is effectively a dead test. The test comment admits the int...

All three addressed in 7273b60:

  • Dead test: Extracted check_create_if_missing_guard(page_exists, config, path) free function from execute_impl and replaced the dead test with three behavioral tests covering all branches (blocked when page missing + disabled, allowed when enabled, allowed when page exists).
  • Duplicate PATH_SEGMENT: Lifted to src/tools/mod.rs as pub(crate) const PATH_SEGMENT; both edit_wiki_page.rs and create_work_item.rs now use use super::PATH_SEGMENT.
  • Null byte check: Added ensure!(!self.path.contains('\0'), "path must not contain null bytes") to EditWikiPageParams::validate(), consistent with create_pr.rs and memory.rs. Updated the existing sanitize test to use \x01 (which passes validation but is stripped by sanitize_fields()) so coverage of both layers is preserved.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • api.github.com (HTTP Only)
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.1-pie /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.1-z /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2now /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2-z /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2relro /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2kxma�� /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2nkx7ezwyyqca5jhkfvfksmjz.1xnos3a.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2tuk7drm704exbz4hyppemjx0.1xnos3a.rcgu.o lib/�� fe1da15ffd/rustc--error-format=json fe1da15ffd/build--json=diagnostic-rendered-ansi,artifacts,future-incompat fe1da15ffd/build--crate-type fe1da15ffd/buildcc e5057-cgu.0.rcgu-m64 e5057-cgu.1.rcgu/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustcRdJOK8/symbols.o lib/rustlib/x86_/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.0auto (packet block)
  • dev.azure.com
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com --allow dev.azure.com --allow *.visualstudio.com /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2gbp02zi6r6wah1ddwpeqlnpu.1xnos3a.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2kxma�� /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2nkx7ezwyyqca5jhkfvfksmjz.1xnos3a.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2tuk7drm704exbz4hyppemjx0.1xnos3a.rcgu.o lib/�� fe1da15ffd/rustc--error-format=json fe1da15ffd/build--json=diagnostic-rendered-ansi,artifacts,future-incompat fe1da15ffd/build--crate-type fe1da15ffd/buildcc e5057-cgu.0.rcgu-m64 e5057-cgu.1.rcgu/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustcRdJOK8/symbols.o lib/rustlib/x86_/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.0-delete (dns block)
  • msazuresphere.visualstudio.com
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com --allow dev.azure.com --allow *.visualstudio.com /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2gbp02zi6r6wah1ddwpeqlnpu.1xnos3a.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2kxma�� /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2nkx7ezwyyqca5jhkfvfksmjz.1xnos3a.rcgu.o /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.2tuk7drm704exbz4hyppemjx0.1xnos3a.rcgu.o lib/�� fe1da15ffd/rustc--error-format=json fe1da15ffd/build--json=diagnostic-rendered-ansi,artifacts,future-incompat fe1da15ffd/build--crate-type fe1da15ffd/buildcc e5057-cgu.0.rcgu-m64 e5057-cgu.1.rcgu/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustcRdJOK8/symbols.o lib/rustlib/x86_/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/proxy_tests-90c56adb783e73e6.0-delete (dns block)
  • raw.github.com
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow *.github.com --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro .o lib/rustlib/x86_/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rmcp_macros-f0ddf4723054b4c8.rcc ac9df4b69.rlib f/zeroize-1.8.2//home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rmcp_macros-f0ddf4723054b4c8.r/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustcC70lV3/symbols.o -57d76dcb25e4827cc e5057-cgu.0.rcgu-m64 e5057-cgu.1.rcgu/home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/rustcRdJOK8/symbols.o .o (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@jamesadevine
Copy link
Collaborator

@copilot the edit-work-item safe output should not allow creation at all. We will add a separate safe output for creation.

Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot the edit-work-item safe output should not allow creation at all. We will add a separate safe output for creation.

Done in be6c846. Removed create-if-missing from EditWikiPageConfig entirely — the tool now only updates pre-existing pages. A 404 response from the GET check immediately returns a failure message directing the caller to use a separate safe output for creation. Updated all related doc comments, log messages, and action strings to say "update" only.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • api.github.com (HTTP Only)
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com -C debuginfo=2 0e408.00mdgrb20p5x77eazk2p9gjyy.0ik56mo.rcgu.o 0e408.01teq1ycpdi29xtq38m58avzw.0ik56mo.rcgu.o 0e408.04wdksz2udhbsmhbmz31ifrn2.0ik56mo.rcgu.o 0e408.0roi7kz8fyd01vr8wopkslw4t.0ik56mo.rcgu.o 0e408.0w0s0sshyajplf2l699oxuwfk.0ik56mo.rcgu.o 0e408.0zz0925kng4sqhjswpq47h7d1.0ik56mo.rcgu.o 0e408.1h8m7eedzoqh87j6ntnde3cky.0ik56mo.rcgu.o 0e408.21isguhevtn32dqcdl1j05fjf.0ik56mo.rcgu.o 0e408.27hw7b2y70hemqls64pwyeeau.0ik56mo.rcgu.o 0e408.2ap69xa5jhcu8bzvi8nf665ru.0ik56mo.rcgu.o 0e408.2l7sln5w36yyxwfvup96uk3iy.0ik56mo.rcgu.o 0e408.2z3o4ny3w29nexl27pg3lovk0.0ik56mo.rcgu.o 0e408.31ku1szu3n6wt3joczv7xzi8i.0ik56mo.rcgu.o 0e408.3q1z69vza816v6i52bhfjdqwn.0ik56mo.rcgu.o (packet block)
  • dev.azure.com
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-65dae42eaa050fb2 /home/REDACTED/work/ado-aw/ado-aw/target/debug/deps/ado_aw-65dae42eaa050fb2 --emit=dep-info,metadata,link -C embed-bitcode=no -C debuginfo=2 --warn=rust_2018_idioms --warn=clippy::zero_sized_map_values --warn=clippy::verbose_file_reads --warn=unused_qualifications --warn=unused_macro_rules --warn=unused_lifetimes 7.th�� 7.thiserror_impl.971f6a7430b99441-cgu.13.rcgu.o 7.thiserror_impl.971f6a7430b99441-cgu.14.rcgu.o 759b226da/build_--crate-type lib/rustlib/x86_/home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/bin/rustc lib/rustlib/x86_--crate-name lib/rustlib/x86_dirs lib/rustlib/x86_--edition=2015 (dns block)
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com --allow dev.azure.com --allow *.visualstudio.com 0e408.04wdksz2udhbsmhbmz31ifrn2.0ik56mo.rcgu.o 0e408.0roi7kz8fyd01vr8wopkslw4t.0ik56mo.rcgu.o 0e408.0w0s0sshyajplf2l699oxuwfk.0ik56mo.rcgu.o 0e408.0zz0925kng4sqhjswpq47h7d1.0ik56mo.rcgu.o 0e408.1h8m7eedzoqh87j6ntnde3cky.0ik56mo.rcgu.o 0e408.21isguhevtn32dqcdl1j05fjf.0ik56mo.rcgu.o 0e408.27hw7b2y70hemqls64pwyeeau.0ik56mo.rcgu.o 0e408.2ap69xa5jhcu8bzvi8nf665ru.0ik56mo.rcgu.o 0e408.2l7sln5w36yyxwfvup96uk3iy.0ik56mo.rcgu.o 0e408.2z3o4ny3w29nexl27pg3lovk0.0ik56mo.rcgu.o 0e408.31ku1szu3n6wt3joczv7xzi8i.0ik56mo.rcgu.o 0e408.3q1z69vza816v6i52bhfjdqwn.0ik56mo.rcgu.o (dns block)
  • msazuresphere.visualstudio.com
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow api.github.com --allow dev.azure.com --allow *.visualstudio.com 0e408.04wdksz2udhbsmhbmz31ifrn2.0ik56mo.rcgu.o 0e408.0roi7kz8fyd01vr8wopkslw4t.0ik56mo.rcgu.o 0e408.0w0s0sshyajplf2l699oxuwfk.0ik56mo.rcgu.o 0e408.0zz0925kng4sqhjswpq47h7d1.0ik56mo.rcgu.o 0e408.1h8m7eedzoqh87j6ntnde3cky.0ik56mo.rcgu.o 0e408.21isguhevtn32dqcdl1j05fjf.0ik56mo.rcgu.o 0e408.27hw7b2y70hemqls64pwyeeau.0ik56mo.rcgu.o 0e408.2ap69xa5jhcu8bzvi8nf665ru.0ik56mo.rcgu.o 0e408.2l7sln5w36yyxwfvup96uk3iy.0ik56mo.rcgu.o 0e408.2z3o4ny3w29nexl27pg3lovk0.0ik56mo.rcgu.o 0e408.31ku1szu3n6wt3joczv7xzi8i.0ik56mo.rcgu.o 0e408.3q1z69vza816v6i52bhfjdqwn.0ik56mo.rcgu.o (dns block)
  • raw.github.com
    • Triggering command: /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw /home/REDACTED/work/ado-aw/ado-aw/target/debug/ado-aw proxy --allow *.github.com -fuse-ld=lld -z 0e408.00mdgrb20p5x77eazk2p9gjyy.0ik56mo.rcgu.o 0e408.01teq1ycpdi29xtq38m58avzw.0ik56mo.rcgu.o 0e408.04wdksz2udhbsmhbmz31ifrn2.0ik56mo.rcgu.o 0e408.0roi7kz8fyd01vr8wopkslw4t.0ik56mo.rcgu.o 0e408.0w0s0sshyajplf2l699oxuwfk.0ik56mo.rcgu.o 0e408.0zz0925kng4sqhjswpq47h7d1.0ik56mo.rcgu.o 0e408.1h8m7eedzoqh87j6ntnde3cky.0ik56mo.rcgu.o 0e408.21isguhevtn32dqcdl1j05fjf.0ik56mo.rcgu.o 0e408.27hw7b2y70hemqls64pwyeeau.0ik56mo.rcgu.o 0e408.2ap69xa5jhcu8bzvi8nf665ru.0ik56mo.rcgu.o 0e408.2l7sln5w36yyxwfvup96uk3iy.0ik56mo.rcgu.o 0e408.2z3o4ny3w29nexl27pg3lovk0.0ik56mo.rcgu.o 0e408.31ku1szu3n6wt3joczv7xzi8i.0ik56mo.rcgu.o 0e408.3q1z69vza816v6i52bhfjdqwn.0ik56mo.rcgu.o (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@jamesadevine jamesadevine marked this pull request as ready for review March 19, 2026 22:35
@jamesadevine jamesadevine merged commit 7b4536f into main Mar 19, 2026
9 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants