/**
* Undo the CLIXML that Windows PowerShell writes to its OWN stderr when we run
* it with -EncodedCommand.
*
* WHY THIS EXISTS AT ALL. `shell: "powershell"` is implemented in exec-shell.ts
* by handing powershell.exe a base64 -EncodedCommand argument — see
* wrapForPowerShell there for why base64 and not quoting (metacharacters do not
* merely break quoting through cmd.exe's parser, they REPARSE into a different
* command). That choice is not negotiable, and it has one measured side effect:
* with -EncodedCommand, PowerShell 5.1 stops writing its error/warning/verbose/
* debug/progress streams as plain text and instead SERIALIZES them to its stderr
* as CLIXML. The caller then receives, verbatim:
*
* #< CLIXML
* …
* Preparing modules for first use.…
*
* on EVERY call (module auto-loading progress), and — much worse — a real error
* comes back shredded. Measured on a real Windows box, `Write-Error
* "this-is-a-real-error"` arrived as:
*
* … : this_x000D__x000A_-is-a-real-error_x000D__x000A_
* + CategoryInfo : NotSpecified: (:) [Write-Error],
* WriteErrorException_x000D__x000A_…
*
* i.e. the message split across elements at PowerShell's own console-width wrap
* points and `_xNNNN_`-escaped. An AI agent reading that has to reverse-engineer
* PowerShell's serializer to recover one sentence. Reassembling it here is what
* turns that back into the text a human sees on a console.
*
* WHAT WAS RULED OUT, MEASURED, so nobody "simplifies" this away:
*
* - `-OutputFormat Text` DOES NOT WORK. Tested against our exact invocation:
* `powershell -NoProfile -NonInteractive -OutputFormat Text -EncodedCommand
* ` produced BYTE-IDENTICAL CLIXML. (An earlier probe that appeared to
* work had used -Command, not -EncodedCommand — the flag was never the cause.
* The trigger is -EncodedCommand itself.) Do not replace this file with a
* flag; the flag was tried on the machine that has the bug.
* - Dropping -EncodedCommand restores the injection hazard it was chosen for,
* and the interior-line-break guarantee that rides on it.
* - A temp .ps1 file with -File breaks the launcher's trust model: the command
* travels only over the bounded stdin protocol, never disk, never a command
* line.
* - Prepending `$ProgressPreference='SilentlyContinue'` silences the progress
* noise but NOT the error serialization — the half that actually hurts — and
* it edits the caller's script, which this layer must not do.
*
* So the decoding happens HERE, on the transport, because the CLIXML is an
* artifact of OUR wrapping choice and the caller's script is untouched by it.
* executor.ts feeds stderr through this ONLY when exec-shell.ts says it did the
* PowerShell wrapping (ExecShellPlan.clixmlStderr); cmd, sh and bash never see
* an instance of this class and are byte-for-byte unaffected.
*
* THE HARD PART, and the reason this is a state machine rather than an XML
* parse: RAW STDERR INTERLEAVES WITH THE SERIALIZED BLOCK. `[Console]::Error.
* WriteLine("direct-stderr-line")` bypasses PowerShell's serializer entirely,
* and measured output put it BETWEEN the marker and the element:
*
* #< CLIXML
* direct-stderr-line
* …
*
* The marker is therefore NOT followed by well-formed XML, and any raw text must
* survive verbatim and in its original position. On top of that the pipe splits
* chunks anywhere — mid-tag, mid-escape — and remote_exec STREAMS stderr, so
* whatever is emitted cannot be recalled. Hence: incremental, per-record,
* bounded, and pass-through-on-doubt.
*
* BYTES, NOT TEXT. Everything below works on latin1 strings — one JS char per
* byte — so any byte sequence round-trips exactly and nothing is transcoded by
* accident (PowerShell writes this stream in the console output encoding, which
* is not necessarily UTF-8 and is not ours to reinterpret). The only place a
* code point is created rather than copied is an unescape, and there it is
* written back as UTF-8 bytes; every escape that occurs in practice
* (_x000D_, _x000A_, _x0009_, _x005F_) is ASCII and identical either way.
*/
/**
* Turn the text content of one CLIXML `` element back into console text.
*
* Two layers, in the order PowerShell applied them in reverse. First XML entity
* references, because the serializer XML-escapes after it escapes control
* characters. Then the `_xHHHH_` form, which is how CLIXML carries anything XML
* cannot: `_x000D_` is CR, `_x000A_` is LF, `_x0009_` is TAB — and `_x005F_` is
* the escape for a LITERAL underscore, which is what makes a single left-to-right
* pass correct. A message containing the text `_x000D_` is serialized as
* `_x005F_x000D_`; scanning left to right consumes `_x005F_` first, yields `_`,
* and leaves `x000D_` as the ordinary characters they were. A right-to-left or
* repeated pass would "helpfully" turn that back into a carriage return.
*/
export declare function decodeClixmlText(text: string): string;
/**
* Incremental CLIXML-on-stderr decoder for the PowerShell path.
*
* ONE INSTANCE PER COMMAND. `push` returns the bytes to forward for that chunk
* (possibly empty); `flush` returns whatever is still held when the command
* ends. Between them nothing is ever dropped except things we positively
* identified as PowerShell's own framing.
*
* WHAT IS TREATED AS POWERSHELL'S FRAMING, and what a hostile command can still
* do. An earlier version of this comment claimed the marker "can only" be
* PowerShell's because a command would have to be first on stderr to forge it.
* That was FALSE, and the code was correspondingly unsafe: PowerShell writes
* `#< CLIXML` at offset 0 on every single call — that is the premise of this
* whole module — so the decoder stayed armed for the rest of the command and
* happily read an `` the COMMAND printed later as its own envelope. A
* script whose stderr ended in `SECRET`
* had those bytes DELETED. Losing a caller's stderr to our parser is strictly
* worse than the noise we remove, so the rule is now positional and narrow:
*
* 1. An envelope is entered ONLY at a place PowerShell alone writes framing:
* directly after a `#< CLIXML` line that sat at stream offset zero, or
* directly after an envelope we ourselves closed (optionally across one line
* break). One `#< CLIXML` arms the decoder for exactly ONE ``; after
* `` it is disarmed until another marker arrives in that position.
* A bare `` in the middle of a command's output is therefore just
* text now, and is forwarded untouched.
* 2. Inside an envelope, anything that is not a record shape we produced is
* forwarded verbatim AND marks the envelope "dirty": from that point on this
* block is no longer purely PowerShell's, so nothing further is deleted from
* it — not the progress records, not even the `` that closes it.
* 3. Character data is never deleted anywhere else. Records are decoded, raw
* interleaved text (whitespace included) is passed through in place, and the
* only bytes dropped are the marker line, the envelope's own tags, the ``
* tags around a record we decoded, and progress records in an undirtied
* envelope.
*
* WHAT REMAINS POSSIBLE, stated honestly. A command that writes `#< CLIXML` at
* offset 0 of stderr — which requires PowerShell to have written nothing at all
* before it — can hand the decoder its own envelope and mangle its OWN bytes in
* a way it chose. A command that prints a CLIXML `…`
* record while PowerShell's own envelope is open loses that record, because at
* that point it is byte-for-byte indistinguishable from the module-autoload
* progress this exists to remove (a caller's real `Write-Progress` is dropped by
* design too). Nothing else: it cannot touch another command's output (one
* instance per command), cannot make the decoder delete ordinary text, cannot
* make it allocate without bound (MAX_PENDING_BYTES), cannot make it throw (pump
* is try/caught into pass-through), and cannot make it reorder anything.
*/
export declare class PowerShellClixmlDecoder {
/**
* `header` — deciding whether what follows is PowerShell's marker.
* `headerRetry` — after an envelope closed: allow one line break, then re-run
* the marker test, so a SECOND serialized block is recognised
* instead of leaking its `#< CLIXML` line as text.
* `headerEol` — marker matched, swallowing the newline that follows it.
* `scan` — armed: everything is raw, watching for this marker's ``: records are decoded, stray text stays raw.
* `raw` — give up, forever: forward every byte untouched.
*/
private mode;
/** Unconsumed input, latin1 (one char per byte). */
private buf;
/**
* Structural bytes we have consumed but not yet committed to dropping — in
* practice the `` opening tag, and the single line break allowed
* before a SECOND `#< CLIXML` marker (headerRetry), which is only framing if
* that marker actually turns up.
*
* It is held rather than dropped so that a block which turns out NOT to be
* something we understand can be forwarded byte-for-byte instead of arriving
* headless. It is discarded the moment one record decodes successfully, and
* flushed ahead of any raw text so ordering is never disturbed.
*
* The `#< CLIXML` line itself is NOT held: matched where only PowerShell
* writes it, it is unambiguously PowerShell's framing, and
* holding it would mean emitting it in front of interleaved raw text in the
* measured `#< CLIXML` / direct-stderr-line / `` case — reintroducing
* exactly the noise this class removes.
*/
private pendingPrefix;
/** Output collected during the current push/flush. */
private out;
/**
* Set when this envelope turned out to contain something we did not produce.
*
* From then on the block is not purely PowerShell's, so nothing more is
* deleted from it — including its own `` and any progress record, which
* in a block a command is writing into may well be the command's. Cleared when
* the next envelope opens.
*/
private envelopeDirty;
push(chunk: Buffer): Buffer;
/**
* End of stream: forward everything still held, verbatim.
*
* A CLIXML block can be cut off mid-record (PowerShell killed, output
* truncated). Emitting the fragment raw is the only option that loses nothing;
* a caller seeing a stray `` tail can still read the message
* inside it, whereas a caller seeing nothing cannot.
*/
flush(): Buffer;
private take;
/**
* Drive the state machine as far as the buffered bytes allow.
*
* Wrapped whole in a try/catch on purpose: a parser bug here would cost a
* caller their stderr, which is strictly worse than the noise we are removing,
* so ANY throw degrades to forwarding the bytes untouched.
*/
private run;
/** Forward everything held and stop decoding for the rest of this command. */
private giveUp;
/** Forward bytes as-is, flushing any held structural prefix ahead of them. */
private emitRaw;
/** A record decoded cleanly: the structural bytes around it are now noise. */
private emitDecoded;
private pump;
/**
* ARMED, between a marker and its envelope. Everything is the command's own
* stderr and is forwarded untouched; we are only watching for the envelope to
* start. Measured: a `[Console]::Error.WriteLine` really does land in here,
* between the marker and `` needs a second `#< CLIXML` in front of it (headerRetry),
* so an `` the command prints later is text, not an envelope.
*
* Returns true if the state changed and pump should keep going.
*/
private scanForObjs;
/**
* True for a progress record we are willing to DELETE.
*
* Progress records are module-autoload noise ("Preparing modules for first
* use.") that PowerShell only serializes because it is talking to a pipe, so
* in a block that is entirely PowerShell's they carry nothing the caller asked
* for. In a block that has already proved to contain something we did not
* produce, the next progress record may be the command's own bytes — and this
* decoder does not delete those. Noise is recoverable; stderr is not.
*/
private isDeletableProgress;
/**
* Inside ``. Consume exactly one child — a record, a stray text run, or
* the closing tag — and say whether pump may continue.
*
* Anything we do not positively recognize is FORWARDED, in place, and marks
* the envelope dirty. It is never guessed at and never dropped, and it no
* longer ends decoding either: a single `<` in a script's own stderr
* (`echo "a&2`, XML output, a nested tool) used to send the rest of the
* command down the pass-through path, which handed every LATER error back in
* exactly the shredded form this module exists to undo.
*/
private parseObjsChild;
/**
* Index just past the `` matching an `` that opened at 0, or -1.
*
* Depth-counted, because a serialized record nests. `` must not read as its close (the
* alternation is ordered so the longer tag wins), and a self-closing ``
* changes no depth.
*/
private findObjEnd;
}