Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
02c7984
Initial plan
Copilot Aug 16, 2026
023b3f0
fix: make create-work-item body field configurable
Copilot Aug 16, 2026
70f67e7
fix: validate create-work-item description field
Copilot Aug 16, 2026
bbda2cb
fix: harden work item markdown sanitization
Copilot Aug 16, 2026
bfefbdb
fix: broaden active html sanitization
Copilot Aug 16, 2026
977785a
fix: handle quoted dangerous html tags
Copilot Aug 16, 2026
d35cae1
fix: scan dangerous html tags safely
Copilot Aug 16, 2026
41b6067
fix: scope html event sanitization to tags
Copilot Aug 16, 2026
a9e5b8c
fix: escape unclosed html tags
Copilot Aug 16, 2026
7ca1523
fix: address create work item review findings
Copilot Aug 16, 2026
2b183f5
fix: make custom field validation deterministic
Copilot Aug 16, 2026
ed52772
fix: validate custom work item fields as field refs
Copilot Aug 16, 2026
270bc6d
test: tighten create work item collision assertions
Copilot Aug 16, 2026
65e3b16
refactor: sanitize work item tag config in place
Copilot Aug 16, 2026
256d00b
fix: clarify default work item body field collisions
Copilot Aug 16, 2026
786379c
refactor: align work item field validation order
Copilot Aug 16, 2026
d32e204
fix: harden markdown html sanitization
Copilot Aug 16, 2026
1b1fa71
test: clarify sanitizer regression name
Copilot Aug 16, 2026
b14f429
fix: preserve fenced markdown code during html sanitization
Copilot Aug 16, 2026
48b6a6f
fix: address final create-work-item review findings
Copilot Aug 17, 2026
7527a96
test: relax folded html sanitizer regression assertion
Copilot Aug 17, 2026
f9f0e62
fix: normalize slash-separated event handler removal
Copilot Aug 17, 2026
fa22b7e
test: use direct folded tag reconstruction case
Copilot Aug 17, 2026
b4fe773
test: avoid exact whitespace in slash handler regression
Copilot Aug 17, 2026
baeb62b
test: document folded tag regression shape
Copilot Aug 17, 2026
1bf6274
test: fix folded dangerous tag regression shape
Copilot Aug 17, 2026
62700c3
refactor: make event handler replacement signature explicit
Copilot Aug 17, 2026
2d0325a
refactor: simplify event handler separator replacement
Copilot Aug 17, 2026
a287f33
test: focus slash handler regression on removed payload
Copilot Aug 17, 2026
eba900a
refactor(sanitize): allowlist-based Markdown sanitizer
Copilot Aug 20, 2026
bed51f4
test(sanitize): cover encoded denied scheme in markdown link
Copilot Aug 20, 2026
2804cd4
test(safe-outputs): verify work item rendering fidelity end to end
Copilot Aug 21, 2026
99099ff
test(safe-outputs): match tilde fences when scoping rendering securit…
Copilot Aug 21, 2026
b8a5087
fix(sanitize): anchor destination lookup after the markdown label
Copilot Aug 21, 2026
94834cf
fix(sanitize): close nested-destination bypass and stop deleting pros…
Copilot Aug 21, 2026
58cd00a
test(sanitize): spell out what a forged sentinel may not do
Copilot Aug 21, 2026
9560d27
Merge origin/main into copilot/update-sanitization-code
jamesadevine Sep 1, 2026
56c29c5
fix(test): account for ADO markdown normalization
jamesadevine Sep 1, 2026
a50ddb7
test(executor-e2e): cover markdown storage guards
jamesadevine Sep 1, 2026
daba362
test(executor-e2e): cover missing format metadata
jamesadevine Sep 1, 2026
e019016
fix(sanitize): close markdown validation gaps
jamesadevine Sep 1, 2026
12e478a
test(executor-e2e): cover Bug rendering success
jamesadevine Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ fail-closed and only pauses when the agent actually proposed a reviewed output.
│ ├── version.rs # Semver parsing/comparison shared across the CLI (pipeline header versions, release tags, codemod cutovers)
│ ├── ndjson.rs # NDJSON parsing utilities
│ ├── sanitize.rs # Input sanitization for safe outputs
│ ├── sanitize/
│ │ └── markdown.rs # Markdown structure parsing plus HTML/URL allowlisting
│ ├── secure.rs # Validated newtype value objects (parse-don't-validate path/identifier types)
│ ├── validate.rs # Structural input validators (char allowlists, format checks, injection detectors)
│ ├── agent_stats.rs # OTel-based agent statistics parsing (token usage, duration, turns)
Expand Down
186 changes: 186 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ indexmap = "2"
zip = { version = "8.6.0", default-features = false, features = ["deflate"] }
semver = "1.0.28"
inventory = "0.3.24"
ammonia = "4.1.4"
pulldown-cmark = { version = "0.13.4", default-features = false }

[dev-dependencies]
reqwest = { version = "0.12", features = ["blocking"] }
Expand Down
53 changes: 52 additions & 1 deletion docs/safe-outputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,57 @@ section). It is additive and build-scoped: it appears as one extra section
alongside any `task.uploadsummary` tabs your own steps publish (including under
`target: job` / `target: stage`), and never collides with them.

### Markdown body sanitization

Safe outputs whose body is stored as Markdown (today: `create-work-item`'s
`description`) go through a Markdown-aware sanitizer rather than blanket HTML
escaping, so headings, lists, tables and code fences survive.

The policy is:

- **Transport sanitization applies to the whole document.** Control characters
and ANSI escapes are removed, Azure DevOps logging commands (`##vso[`, `##[`)
are wrapped in backticks, HTML comments are removed, and the content size and
line count caps are enforced.
- **Code is left alone.** [pulldown-cmark](https://docs.rs/pulldown-cmark)
identifies code spans, fenced and indented code blocks; no rendering
transformation is applied inside them, so a fence showing `<script>` or
`@mention` stays readable.
- **Inline HTML is allowlisted, not blocklisted.**
[ammonia](https://docs.rs/ammonia) keeps a small set of formatting tags that
have a Markdown equivalent (`a`, `b`/`strong`, `i`/`em`, `code`, `pre`,
headings, lists, tables, `img`, `blockquote`, …) with a small attribute
allowlist (`href`, `src`, `alt`, `title`, `width`, `height`, `colspan`,
`rowspan`, `align`). Everything else — `script`, `style`, `iframe`, `form`,
SVG/MathML, `on*` handlers, `style`/`class`/`id` attributes — is dropped;
`script` and `style` also lose their contents.
- **URLs are scheme-allowlisted.** `http`, `https`, `mailto` and relative
destinations are allowed everywhere (HTML attributes, Markdown links and
images, autolinks and reference definitions). Any other scheme is dropped from
HTML attributes and replaced with `(redacted)` in Markdown destinations.
- **Mentions and bot triggers are neutralized outside code.** `@name`,
`fixes #123` and `AB#123` are wrapped in backticks so they cannot notify
people or link work items.

Text that only looks like markup is escaped rather than deleted: `Vec<String>`
in prose is stored as `Vec&lt;String&gt;`, which Markdown renders as the author
wrote it, and that escaping is also what stops dropped markup from being
re-parsed. Markup a browser really would parse as a tag still goes to the
allowlist and is dropped there, so a dangerous payload never reappears as
visible text.

Cleaning normalizes HTML, so a few inputs come back rendering the same but
written differently: `\r\n` becomes `\n`, `<br />` becomes `<br>`, a table gains
an implied `<tbody>`, a mid-line `>` is stored as `&gt;`, and the content of a
removed raw-text element such as `<noscript>` is escaped rather than kept as
markup.

Azure DevOps then applies its own server-side Markdown sanitization when the
work item is stored. That service-side pass may further normalize safe HTML and
remove dangerous HTML even when it appeared inside a code fence. The compiler
tests pin ado-aw's pre-storage output separately from the executor E2E fixture
that pins the representation returned by Azure DevOps.

### Executor authentication

All write-bearing safe outputs (e.g. `create-pull-request`,
Expand Down Expand Up @@ -842,7 +893,7 @@ field that receives the body.

**Agent parameters:**
- `title` - A concise title for the work item (required, must be more than 5 characters)
- `description` - Work item description in Markdown format (required, must be more than 30 characters). Inline HTML is preserved for Azure DevOps to render/sanitize.
- `description` - Work item description in Markdown format (required, must be more than 30 characters). Markdown is preserved; see [Markdown body sanitization](#markdown-body-sanitization) for the HTML and URL policy applied to it.
- `tags` - Tags to apply to the work item (optional list; each tag must not contain a semicolon). May be subject to the `allowed-tags` allowlist. Merged with any static `tags` configured in front matter.

On success, the MCP tool returns a generated gh-aw-compatible `#aw_...`
Expand Down
Loading
Loading