Categorize a ysonet gadget
Classify only facts supported by the gadget code, tests, or project
documentation. Keep missing facts visible as uncategorized; never guess a
more useful-looking value.
1. Read the evidence
Read:
CLAUDE.md and the gadget sections of docs/ARCHITECTURE.md.
- The complete generator, including
Generate, Options, Variants, Labels,
AdditionalInfo, CommandInput, and SupportedFormatters.
- Any inner generator, bridge, helper, bundled assembly, or target type on which
the gadget depends.
- Focused tests for the gadget and its variants.
Confirm whether GadgetFacetSet and GadgetFacetReader exist. If they do not,
report proposed values only and say that the category implementation is pending.
Do not create a parallel metadata scheme.
2. Classify effective capability units
- A gadget without variants has one unit.
- Variants with identical facts inherit the gadget facets.
- A variant with different kind, accepted input, or requirements gets a complete
FacetOverride.
- Never combine one variant's formatter, input, or requirements with another's.
- A gadget that subclasses ANOTHER gadget (not
GenericGenerator directly, for
example ActivitySurrogateSelectorFromFile extends ActivitySurrogateSelector,
and DataSetTypeSpoof extends DataSet) inherits the parent gadget's Facets()
unless it overrides them. Its capability often differs from the parent, so
classify it on its own evidence and give it its own Facets() instead of
trusting the inherited default.
Formatter is already metadata. Start with SupportedFormatters() and remove the
variant's UnsupportedFormatters.
3. Use the small vocabulary
Every declared axis can contain multiple proven values.
Payload kind
Choose broad discovery families only:
code-execution
file-system
network
information-disclosure
denial-of-service
nested-deserialization
other
uncategorized
Do not create values for an individual sink, CVE, protocol, or operation. For
example, file read/write/delete can share file-system, and DNS/SMB/callbacks can
share network, when the behavior is proven. A working-directory change is not
automatically file-system: use other if the behavior is known but does not fit
the broad vocabulary, or uncategorized if the behavior is not established.
Input type is not payload kind. Reading a source file on the generator does not
make the target payload an information-disclosure gadget.
Accepted input
Use these user-provided forms:
command
local-file (read on the OPERATOR machine while building)
target-path (a path only the TARGET process touches)
unc-path
host (a bare host name or IP the target reaches)
remote-url
source-code-file
assembly-file
none
other
uncategorized
Normally omit .WithInputs(...) and let the reader derive the value from the
effective CommandInputType. The full table is GadgetFacetReader.DeriveInput,
which has one arm per enum member:
| CommandInputType |
Accepted input |
ShellCommand |
command |
CsSourceFile |
source-code-file |
DllPath |
assembly-file |
UncPath |
unc-path |
HostName |
host |
Url |
remote-url |
FilePath |
local-file |
TargetPath |
target-path |
TargetPathPair |
target-path |
TargetPathAndLocalFile |
target-path |
Ignored |
none |
The path types say WHOSE file system a path belongs to, and that is the one thing
a user has to get right on a file gadget: local-file is read here while
building, target-path is only touched by the deserializing process. Do not fold
them together.
Override the derived value only when code proves additional or different forms:
both local-file and unc-path for a gadget that accepts either, or
remote-url plus target-path for one whose -c is a URL and whose second
option names a file on the target. Distinguish a file ysonet consumes from a path
the generated payload uses in the detailed help.
Requirements
Use broad target needs:
built-in
extra-assembly
wpf
net-framework
modern-dotnet
other
uncategorized
Multiple requirements can apply. Do not confuse a generator build dependency
with a target requirement. Keep exact assembly names, products, and versions in
AdditionalInfo() or Labels().
other means a proven fact falls outside the vocabulary. uncategorized means
the evidence is missing or has not been reviewed. Never combine
uncategorized with another value on the same axis.
Runtime versions
This axis is the one exception to the broad-vocabulary rule: it carries exact
build numbers, because "old build" does not tell an operator whether the payload
lands. Tokens live in RuntimeVersion: net-fx-2.0 through net-fx-4.8.1,
net-5.0 through net-10.0, mono, plus other and unspecified.
- THE VERSION DESCRIBES THE TARGET, never ysonet and never the machine the
payload was built on. Ask "what does the operator have to check on the app in
front of them". That is usually the framework the target PROCESS RUNS ON, but
when the gate is a compile-time compatibility switch it is the framework the
target APPLICATION WAS BUILT AGAINST (its
TargetFrameworkAttribute). Both are
versions and both get declared. DataViewManagerXxe and DataSetXxe are the
worked example: EnableLegacyXmlSettings() reads the entry assembly's
attribute, so an app stamped below 4.5.2 is exploitable on a fully patched
machine and one stamped 4.5.2+ is not on any build - they declare 4.0 - 4.5.1,
and that span is about the app. Both were wrongly left unspecified at first
because the reviewer measured ysonet's own build instead of the target's.
- A new or changed runtime-gated gadget must name at least one evidence-backed
working version. Test the current/latest candidate first. If it does not fire
because of runtime compatibility, reproduce on older supported target
versions and use the highest verified working version, never the failed latest
version. Record the latest tested non-working version in
AdditionalInfo() or
the gadget docs.
- Use a single token when only one target version is established. Declare a contiguous
span with
RuntimeVersion.Range(first, last) only when evidence supports the
whole span; Range refuses a reversed pair and one that crosses runtime
families.
- A declaration means "reproduced or documented here", never "fails everywhere
else". An unlisted version means nobody recorded it.
- Leave
unspecified when the real gate is not a version at all: an OS patch
(PSObject and CVE-2017-8565), a library version, or a machine-wide switch
somebody can toggle. That detail belongs in AdditionalInfo(). A gate that IS
a framework version threshold does not qualify, even when the threshold is on
the target app's build rather than the installed runtime - declare it. For an
existing gadget, missing version evidence can remain visibly unspecified;
for a new runtime-gated gadget it is an unresolved finding, not a finished
declaration.
- Never fill this axis in to make a gadget look better documented.
unspecified
is the honest and expected value for most of the catalog.
The FLOOR is measured too, not assumed
The ceiling rule above has a mirror. Most of the catalog declares a 4.0 floor
because 2.0 - 3.5 is a different CLR nobody had run these on, not because anyone
measured a refusal. The LEGACY test tier (ysonet.Tests.exe --legacy) measures
it: it deserializes the real payload on CLR 2 in lanes for 2.0, 3.0 and 3.5.
- Cheap static check first, and it is free: a NORMAL run prints
LEGACY floor candidates, the gadgets whose generated payload names NO 4.x
assembly version. The most common CLR-2 blocker is an assembly VERSION string
in our own payload rather than an absent type, so that list is the filter.
- Then add a row to
LegacyClrRows in ysonet.Tests/Tiers/LegacyClrTier.cs (a private
module uses the RunPrivateLegacyRows hook into the same engine) and run the
tier.
- A gadget earns a low token only when ALL FOUR hold: the child reported
Environment.Version 2.0.50727 (asserted, not assumed - the .exe.config
pin is not proof); the row's real EFFECT was observed, never "no exception";
no assembly from a newer framework was recorded as loaded; and the lane's
reference set matches the token being claimed.
- A
net-fx-2.0 claim means 2.0 at the servicing level the run header prints.
Installing 3.5 SP1 service-packs the 2.0 files in place, and a 2.0-only box is
not installable on modern Windows, so a 2.0-RTM answer is out of reach here.
- Record both bounds, or say explicitly that the floor was not measured. A
measured NEGATIVE is a result worth keeping: put the classified reason in
AdditionalInfo() (for example "below 4.0 only BinaryFormatter and
LosFormatter work, because the SOAP / NetDataContract / DataContract readers
bind the payload's Version=4.0.0.0 verbatim"), never in a facet value.
- The version-evidence check is symmetric, so a floor observation is REPORTED
(
couldLower) rather than failing the run. Do not read a green run as
"nothing to declare"; read the advisory lines.
4. Apply requested changes
When the user asks for edits:
- Override
Facets() for the normal gadget facts. Build the set fluently:
new GadgetFacetSet().WithKinds(...).WithRequirements(...). Each WithKinds,
WithInputs, WithRequirements, and WithVersions REPLACES its whole axis. The
constructor defaults Kinds and Requirements to uncategorized, Versions to
unspecified, and leaves Inputs null so the reader derives accepted input from
the effective CommandInputType. Omit WithInputs(...) whenever that derived
value is correct, and omit WithVersions(...) unless the evidence names versions.
- Add a complete
FacetOverride only to a variant that differs, via
variant.WithFacets(new GadgetFacetSet()...). The override must declare full
Kinds, Requirements, and Versions (it replaces the whole set, so a version the
gadget declared is lost unless repeated); leave its Inputs null when the variant's
effective Input derives the right value.
- Keep metadata beside the gadget; do not add a production name-to-facet table.
- Correct stale
Labels() or AdditionalInfo() found during the review.
- Update the gadget row and facet contract in
docs/ARCHITECTURE.md.
- Add focused coverage for meaningful variant distinctions or new values.
- Confirm
--category=axis=value, filtered --list gadgets, and gadget help
expose each effective unit correctly.
- Run the project's normal Debug build.
Do not change payload generation to make a category convenient. Do not add plugin
metadata. For a catalog-wide consistency review, use
$ysonet-audit-gadget-metadata.
5. Report the result
Report one row per effective unit:
| Unit |
Payload kind |
Formatters |
Accepted input |
Requirements |
Runtime versions |
| Gadget or variant |
values |
values |
values |
values |
values |
Call out inherited facts, every uncategorized or unspecified axis and its missing evidence,
exact target dependencies, changed files, and verification results.
Final checks
- Every fact has code, test, or project-documentation evidence.
- Values are broad, and multiple proven values are retained.
- Input is derived unless an override is necessary.
- Variants remain internally consistent.
other and uncategorized retain different meanings.
- Every new runtime-gated gadget names at least one verified working version; if
latest failed, the highest verified working and latest tested non-working
versions are recorded.
- Runtime versions are declared only where evidence names them, with a single
token or an evidence-backed contiguous range, never to look complete.
- Formatter values match effective variant support.
- No plugin facet work was introduced.
1---2name: ysonet-categorize-gadget3description: Categorize or review a ysonet gadget and its variants by broad payload kind, formatter, accepted input, target requirements, and the runtime versions the effect is recorded on. Use when adding or changing a gadget, filling uncategorized or unspecified metadata, or checking category search and gadget-help output. Do not use for plugins, which intentionally have no category filter.4---56# Categorize a ysonet gadget78Classify only facts supported by the gadget code, tests, or project9documentation. Keep missing facts visible as `uncategorized`; never guess a10more useful-looking value.1112## 1. Read the evidence1314Read:1516- `CLAUDE.md` and the gadget sections of `docs/ARCHITECTURE.md`.17- The complete generator, including `Generate`, `Options`, `Variants`, `Labels`,18 `AdditionalInfo`, `CommandInput`, and `SupportedFormatters`.19- Any inner generator, bridge, helper, bundled assembly, or target type on which20 the gadget depends.21- Focused tests for the gadget and its variants.2223Confirm whether `GadgetFacetSet` and `GadgetFacetReader` exist. If they do not,24report proposed values only and say that the category implementation is pending.25Do not create a parallel metadata scheme.2627## 2. Classify effective capability units2829- A gadget without variants has one unit.30- Variants with identical facts inherit the gadget facets.31- A variant with different kind, accepted input, or requirements gets a complete32 `FacetOverride`.33- Never combine one variant's formatter, input, or requirements with another's.34- A gadget that subclasses ANOTHER gadget (not `GenericGenerator` directly, for35 example `ActivitySurrogateSelectorFromFile` extends `ActivitySurrogateSelector`,36 and `DataSetTypeSpoof` extends `DataSet`) inherits the parent gadget's `Facets()`37 unless it overrides them. Its capability often differs from the parent, so38 classify it on its own evidence and give it its own `Facets()` instead of39 trusting the inherited default.4041Formatter is already metadata. Start with `SupportedFormatters()` and remove the42variant's `UnsupportedFormatters`.4344## 3. Use the small vocabulary4546Every declared axis can contain multiple proven values.4748### Payload kind4950Choose broad discovery families only:5152- `code-execution`53- `file-system`54- `network`55- `information-disclosure`56- `denial-of-service`57- `nested-deserialization`58- `other`59- `uncategorized`6061Do not create values for an individual sink, CVE, protocol, or operation. For62example, file read/write/delete can share `file-system`, and DNS/SMB/callbacks can63share `network`, when the behavior is proven. A working-directory change is not64automatically `file-system`: use `other` if the behavior is known but does not fit65the broad vocabulary, or `uncategorized` if the behavior is not established.6667Input type is not payload kind. Reading a source file on the generator does not68make the target payload an information-disclosure gadget.6970### Accepted input7172Use these user-provided forms:7374- `command`75- `local-file` (read on the OPERATOR machine while building)76- `target-path` (a path only the TARGET process touches)77- `unc-path`78- `host` (a bare host name or IP the target reaches)79- `remote-url`80- `source-code-file`81- `assembly-file`82- `none`83- `other`84- `uncategorized`8586Normally omit `.WithInputs(...)` and let the reader derive the value from the87effective `CommandInputType`. The full table is `GadgetFacetReader.DeriveInput`,88which has one arm per enum member:8990| CommandInputType | Accepted input |91|---|---|92| `ShellCommand` | `command` |93| `CsSourceFile` | `source-code-file` |94| `DllPath` | `assembly-file` |95| `UncPath` | `unc-path` |96| `HostName` | `host` |97| `Url` | `remote-url` |98| `FilePath` | `local-file` |99| `TargetPath` | `target-path` |100| `TargetPathPair` | `target-path` |101| `TargetPathAndLocalFile` | `target-path` |102| `Ignored` | `none` |103104The path types say WHOSE file system a path belongs to, and that is the one thing105a user has to get right on a file gadget: `local-file` is read here while106building, `target-path` is only touched by the deserializing process. Do not fold107them together.108109Override the derived value only when code proves additional or different forms:110both `local-file` and `unc-path` for a gadget that accepts either, or111`remote-url` plus `target-path` for one whose `-c` is a URL and whose second112option names a file on the target. Distinguish a file ysonet consumes from a path113the generated payload uses in the detailed help.114115### Requirements116117Use broad target needs:118119- `built-in`120- `extra-assembly`121- `wpf`122- `net-framework`123- `modern-dotnet`124- `other`125- `uncategorized`126127Multiple requirements can apply. Do not confuse a generator build dependency128with a target requirement. Keep exact assembly names, products, and versions in129`AdditionalInfo()` or `Labels()`.130131`other` means a proven fact falls outside the vocabulary. `uncategorized` means132the evidence is missing or has not been reviewed. Never combine133`uncategorized` with another value on the same axis.134135### Runtime versions136137This axis is the one exception to the broad-vocabulary rule: it carries exact138build numbers, because "old build" does not tell an operator whether the payload139lands. Tokens live in `RuntimeVersion`: `net-fx-2.0` through `net-fx-4.8.1`,140`net-5.0` through `net-10.0`, `mono`, plus `other` and `unspecified`.141142- THE VERSION DESCRIBES THE TARGET, never ysonet and never the machine the143 payload was built on. Ask "what does the operator have to check on the app in144 front of them". That is usually the framework the target PROCESS RUNS ON, but145 when the gate is a compile-time compatibility switch it is the framework the146 target APPLICATION WAS BUILT AGAINST (its `TargetFrameworkAttribute`). Both are147 versions and both get declared. `DataViewManagerXxe` and `DataSetXxe` are the148 worked example: `EnableLegacyXmlSettings()` reads the entry assembly's149 attribute, so an app stamped below 4.5.2 is exploitable on a fully patched150 machine and one stamped 4.5.2+ is not on any build - they declare 4.0 - 4.5.1,151 and that span is about the app. Both were wrongly left `unspecified` at first152 because the reviewer measured ysonet's own build instead of the target's.153- A new or changed runtime-gated gadget must name at least one evidence-backed154 working version. Test the current/latest candidate first. If it does not fire155 because of runtime compatibility, reproduce on older supported target156 versions and use the highest verified working version, never the failed latest157 version. Record the latest tested non-working version in `AdditionalInfo()` or158 the gadget docs.159- Use a single token when only one target version is established. Declare a contiguous160 span with `RuntimeVersion.Range(first, last)` only when evidence supports the161 whole span; `Range` refuses a reversed pair and one that crosses runtime162 families.163- A declaration means "reproduced or documented here", never "fails everywhere164 else". An unlisted version means nobody recorded it.165- Leave `unspecified` when the real gate is not a version at all: an OS patch166 (PSObject and CVE-2017-8565), a library version, or a machine-wide switch167 somebody can toggle. That detail belongs in `AdditionalInfo()`. A gate that IS168 a framework version threshold does not qualify, even when the threshold is on169 the target app's build rather than the installed runtime - declare it. For an170 existing gadget, missing version evidence can remain visibly `unspecified`;171 for a new runtime-gated gadget it is an unresolved finding, not a finished172 declaration.173- Never fill this axis in to make a gadget look better documented. `unspecified`174 is the honest and expected value for most of the catalog.175176#### The FLOOR is measured too, not assumed177178The ceiling rule above has a mirror. Most of the catalog declares a 4.0 floor179because 2.0 - 3.5 is a different CLR nobody had run these on, not because anyone180measured a refusal. The LEGACY test tier (`ysonet.Tests.exe --legacy`) measures181it: it deserializes the real payload on CLR 2 in lanes for 2.0, 3.0 and 3.5.182183- Cheap static check first, and it is free: a NORMAL run prints184 `LEGACY floor candidates`, the gadgets whose generated payload names NO 4.x185 assembly version. The most common CLR-2 blocker is an assembly VERSION string186 in our own payload rather than an absent type, so that list is the filter.187- Then add a row to `LegacyClrRows` in `ysonet.Tests/Tiers/LegacyClrTier.cs` (a private188 module uses the `RunPrivateLegacyRows` hook into the same engine) and run the189 tier.190- A gadget earns a low token only when ALL FOUR hold: the child reported191 `Environment.Version` `2.0.50727` (asserted, not assumed - the `.exe.config`192 pin is not proof); the row's real EFFECT was observed, never "no exception";193 no assembly from a newer framework was recorded as loaded; and the lane's194 reference set matches the token being claimed.195- A `net-fx-2.0` claim means 2.0 at the servicing level the run header prints.196 Installing 3.5 SP1 service-packs the 2.0 files in place, and a 2.0-only box is197 not installable on modern Windows, so a 2.0-RTM answer is out of reach here.198- Record both bounds, or say explicitly that the floor was not measured. A199 measured NEGATIVE is a result worth keeping: put the classified reason in200 `AdditionalInfo()` (for example "below 4.0 only BinaryFormatter and201 LosFormatter work, because the SOAP / NetDataContract / DataContract readers202 bind the payload's `Version=4.0.0.0` verbatim"), never in a facet value.203- The version-evidence check is symmetric, so a floor observation is REPORTED204 (`couldLower`) rather than failing the run. Do not read a green run as205 "nothing to declare"; read the advisory lines.206207## 4. Apply requested changes208209When the user asks for edits:2102111. Override `Facets()` for the normal gadget facts. Build the set fluently:212 `new GadgetFacetSet().WithKinds(...).WithRequirements(...)`. Each `WithKinds`,213 `WithInputs`, `WithRequirements`, and `WithVersions` REPLACES its whole axis. The214 constructor defaults Kinds and Requirements to `uncategorized`, Versions to215 `unspecified`, and leaves Inputs null so the reader derives accepted input from216 the effective `CommandInputType`. Omit `WithInputs(...)` whenever that derived217 value is correct, and omit `WithVersions(...)` unless the evidence names versions.2182. Add a complete `FacetOverride` only to a variant that differs, via219 `variant.WithFacets(new GadgetFacetSet()...)`. The override must declare full220 Kinds, Requirements, and Versions (it replaces the whole set, so a version the221 gadget declared is lost unless repeated); leave its Inputs null when the variant's222 effective `Input` derives the right value.2233. Keep metadata beside the gadget; do not add a production name-to-facet table.2244. Correct stale `Labels()` or `AdditionalInfo()` found during the review.2255. Update the gadget row and facet contract in `docs/ARCHITECTURE.md`.2266. Add focused coverage for meaningful variant distinctions or new values.2277. Confirm `--category=axis=value`, filtered `--list gadgets`, and gadget help228 expose each effective unit correctly.2298. Run the project's normal Debug build.230231Do not change payload generation to make a category convenient. Do not add plugin232metadata. For a catalog-wide consistency review, use233`$ysonet-audit-gadget-metadata`.234235## 5. Report the result236237Report one row per effective unit:238239| Unit | Payload kind | Formatters | Accepted input | Requirements | Runtime versions |240|---|---|---|---|---|---|241| Gadget or variant | values | values | values | values | values |242243Call out inherited facts, every `uncategorized` or `unspecified` axis and its missing evidence,244exact target dependencies, changed files, and verification results.245246## Final checks247248- Every fact has code, test, or project-documentation evidence.249- Values are broad, and multiple proven values are retained.250- Input is derived unless an override is necessary.251- Variants remain internally consistent.252- `other` and `uncategorized` retain different meanings.253- Every new runtime-gated gadget names at least one verified working version; if254 latest failed, the highest verified working and latest tested non-working255 versions are recorded.256- Runtime versions are declared only where evidence names them, with a single257 token or an evidence-backed contiguous range, never to look complete.258- Formatter values match effective variant support.259- No plugin facet work was introduced.