Synthèse automatique
Réalise un audit approfondi des deux frontends du compilateur Papyrus (lecteur .pex et décompilateur en cinq phases, lexeur et analyseur Pratt .psc) pour détecter les défauts liés aux entrées non fiables et à la justesse de l'AST produit.
Points forts
- Structure l'audit en quatre dimensions distinctes avec des agents de tâche et une limite de concurrence explicite.
- Définit des règles d'escalade de sévérité adaptées au domaine : panic, OOB ou allocation non bornée depuis des octets non fiables passent en HIGH, CRITICAL si la mémoire n'est pas sûre.
- Prévoit une approche delta-first via git log et des instructions pour re-vérifier les garde-fous plutôt que relire tout le code.
- S'appuie sur des instruments concrets du dépôt (smoke test de corpus, désassemblage d'une fonction, extraction de .pex depuis les BA2) et sur une référence externe, Champollion.
Limites
- Très spécifique au dépôt ByroRedux : les chemins, crates, documents de conception et commandes gh cités n'ont pas de sens ailleurs.
- Suppose que les fichiers de protocole partagés (_audit-common.md, _audit-severity.md) et les rapports d'audit antérieurs soient présents et à jour.
- Ne couvre pas les recognizers, le runtime ECS, le format hkx ni le décodage VMAD de l'ESM, qui relèvent d'autres skills.
À utiliser lorsqu'il faut évaluer la robustesse face à des fichiers .pex ou .psc malformés, ou vérifier qu'aucune passe du décompilateur ne produit un AST erroné accepté ensuite par un recognizer.
À éviter pour une revue de code générique, pour auditer le runtime ou l'ECS, ou en dehors de l'arborescence et de l'outillage du projet ByroRedux.
Analyse de sécurité
PrudenceThe skill is a repository-specific static/dynamic audit playbook for the ByroRedux Papyrus frontends. It performs no secret handling, no base64/obfuscated payloads, and no outbound data transfer beyond read-only GitHub issue queries; all commands (cargo test, git log, gh issue list, mkdir, scoped rm -rf under /tmp) are ordinary developer tooling used for a legitimate purpose. The only concerns are the mismatch between the empty declared-tool list and the shell/network actions the instructions require, plus writing a report into the target repo, which warrants 'caution' rather than 'safe' or 'risky'.
- •Capability mismatch: <declared_allowed_tools> is empty, yet the skill's workflow requires shell execution (mkdir -p /tmp/audit/papyrus, cargo test -p byroredux-pex/papyrus, cargo run --example ..., git log, rm -rf /tmp/audit/papyrus), filesystem writes into docs/audits/, and network access via `gh issue list --repo matiaszanolli/ByroRedux`.
- •Destructive command present but tightly scoped: Phase 4 runs `rm -rf /tmp/audit/papyrus`; Phase 1 recreates the directory, so blast radius is limited to an audit scratch dir, but a mistyped path would delete broadly.
- •Skill writes a report artifact into the target repository (docs/audits/AUDIT_PAPYRUS_<TODAY>.md), which mutates the audited project's tree.
- •Requires large third-party game archives (Skyrim .bsa, Fallout 4 .ba2) as untrusted-input corpora and executes a Rust example binary over them; this is the intended purpose but is real code execution over attacker-controlled bytes (mitigated because the audit's whole goal is finding those bounds bugs).
- •No exfiltration, obfuscation, credential access, curl|sh piping, or attempts to alter the auditor's role were found in the content; references to specific commit hashes / issue numbers are unverifiable but not a risk.
- •Skill drives shell/repo tooling: `mkdir`, `git log`, `cargo test`, and cleanup `rm -rf /tmp/audit/papyrus` (scoped to a temp dir, benign but is a recursive delete).
- •Network access via `gh issue list --repo matiaszanolli/ByroRedux` (GitHub API) — pulls public issue metadata only, no credential use or exfiltration.
- •Runs repository code: `cargo run --release -p byroredux-pex --example pex_corpus_smoke -- <archives>` compiles and executes example binaries from the repo under audit, which is inherent to the task but is arbitrary code execution relative to the repo.
- •No prompt-injection/role-change attempt detected inside the skill body; content is a legitimate audit playbook.
Exemples
/audit-papyrus --focus all --depth deep/audit-papyrus --focus pex-reader --depth shallow — check bounds and allocation caps on untrusted .pex input in crates/pex/src/reader.rs/audit-papyrus --focus decompiler --depth deep — trace each decompile pass and flag any rewrite that could yield a wrong AST accepted by a recognizerdescription: "Deep audit of the Papyrus compiler frontends — .pex reader + 5-phase decompiler (Champollion port, crates/pex) and the .psc lexer/Pratt parser (crates/papyrus): untrusted-input bounds, decompiler soundness, depth caps" argument-hint: "--focus <dimensions> --depth shallow|deep"
Papyrus Frontend Audit (crates/pex + crates/papyrus)
Read .claude/commands/_audit-common.md and .claude/commands/_audit-severity.md for shared protocol.
Audit the two frontends that turn Papyrus bytes into the shared byroredux_papyrus::ast::Script:
untrusted .pex bytecode → decode (crates/pex) → 5-phase decompiler, and .psc source → lexer + Pratt
parser (crates/papyrus). Both feed the recognizer chain in /audit-scripting (which owns
translate_pex's clean-None contract and the panic net); this skill owns everything up to the AST.
Highest bug-density area of the scripting domain (untrusted input + five tree-rewriting passes).
Findings here are silent wrong-AST (a recognizer then matches it → wrong ECS behavior on vanilla
content) or process aborts (OOM / stack overflow — catch_unwind cannot recover either, so a panic net
is no substitute for a bound).
Architecture: Orchestrator; each dimension is a Task agent (max 3 concurrent).
Scope
crates/pex/src/:reader.rs,opcode.rs,model.rs,lib.rs(parse,PexError,string_byte_budget),call_sites.rs(Pex::call_sites, the extender-preflight scan — consumed by/audit-scriptingDim 7),decompile/{mod,cfg,lift,boolean,control_flow,lower,node,event_names}.rs.crates/papyrus/src/:token.rs,lexer.rs,ast.rs,span.rs,error.rs,lib.rs,parser/{mod,expr,stmt,script}.rs.- Instruments:
crates/pex/examples/pex_corpus_smoke.rs(decompiles every.pexin real archives; the source of the decompile-rate claim),pex_dump_fn.rs(raw-bytecode disassembly of one failing function),pex_corpus_shapes.rs+docs/r5/corpus-shape-survey.txt,crates/bsa/examples/r5_extract_pex_ba2.rs(corpus extractor). - Ground truth: crate docstrings (
crates/pex/src/lib.rs,decompile/mod.rs),docs/engine/{papyrus-parser, m47-2-design}.md("no opcode semantics guessed"). Reference decompiler: Champollion (OPCODES,PscCoder::writeStates); UESP Papyrus Assembly spec for operand order. - Not here: hkx (
/audit-parsers), ESM VMAD decode (/audit-esm), recognizers/runtime (/audit-scripting).
Parameters / Extra Fields / Severity
--focus default all 4 · --depth shallow (bounds + depth-cap contracts) | deep (trace each pass's tree
rewrite). Finding fields: Dimension: PEX Reader | Decompiler CFG & Lift | Decompiler Boolean/Control-Flow/
Lower | Papyrus Lexer & Parser · Untrusted-Input: Yes | No (Yes for any path consuming raw .pex/.psc).
Escalations over _audit-severity.md: panic / OOB / unbounded alloc or recursion reachable from untrusted
bytes → HIGH (CRITICAL if memory-unsafe — the transmute in opcode.rs); decompiler emits a wrong AST
that a recognizer accepts, or copy-propagation / boolean-collapse folds into the wrong consumer → HIGH
(silent, all-game blast radius); doc rot → LOW.
Phase 1: Setup
mkdir -p /tmp/audit/papyrus; dedup gh issue list --repo matiaszanolli/ByroRedux --limit 300 --json number,title,state,labels > /tmp/audit/papyrus/issues.json;
read the newest docs/audits/AUDIT_SCRIPTING_*.md (Dims 1-4 findings; pre-split reports use the old
8-dimension numbering) and docs/engine/m47-2-design.md §"Frontends in detail"/"Risks & mitigations" to
separate designed declines from defects. Run cargo test -p byroredux-pex -p byroredux-papyrus.
Delta-first: git log --since=<last report> --format='%h %cs %s' -- crates/pex crates/papyrus — these crates
are quiet between some passes (and busy in others — M47.2 churned lower.rs and the parser's script items);
if there are none, re-verify the guards below rather than re-reading source.
Phase 2: Dimensions
Dimension 1: .pex Reader & Opcode Decode (untrusted input — every finding is Untrusted-Input: Yes)
Paths: crates/pex/src/{reader,opcode,model,lib,call_sites}.rs
First step: cargo test -p byroredux-pex (then re-read any guard named below that did not run)
take(n)is the single bounds gate (checked_add+<= len→UnexpectedEof). No read path may indexself.data[..]ortry_into().unwrap()a short slice. Guard:every_prefix_of_every_wire_valid_ sample_is_rejected(exhaustive-prefix over five builders: Skyrim LE/BE, extender-dependent BE, Starfield guards, and #4474'sbuild_sample_full_coverage— FO4 debug-info skips, getter/setter bodies, non-emptystruct_infos,Value::Float). A new reader branch no sample reaches is the gap to look for.- Allocation is bounded by something other than a wire count: var-arg count
n(up toi32::MAX) must not feedVec::with_capacity(grow bypush;hostile_vararg_count_errors_instead_of_ooming); otherwith_capacity(count)sites areu16-capped;u32flags/sizes are never capacities. String copies are charged againststring_byte_budget=max(64 × file size, 16 MiB)in bothreader.rsandcall_sites.rs(ScanBudget; #4317 — a ~330 KB wire-valid file once exhausted 8 GB and aborted). Any new owned copy of a table string (a new model field, a new scan) must be charged; guardsstring_copies_past_the_budget_are_refused,call_site_copies_are_bounded_by_the_string_budget. OpCode::from_u8transmute(unsafe, memory-safety-critical):MAX_OPCODE == 51equals last discriminant + 1, enum#[repr(u8)]with contiguous0..=50, guard is>=; a compile-timeconst _: () = assert!(OpCode::TryLockGuards as u8 == MAX_OPCODE - 1)backs it (#4475). Pins:discriminants_match_on_disk_order,from_u8_round_trips_and_rejects_oob,metadata_matches_champollion{,_full_table}(verify they cover every discriminant, not spot values).arg_counttable drives operand consumption: a wrong row desyncs the whole stream silently. Spot-check var-arg opcodes (callmethod/callparent/callstatic/guard ops) and high-arity rows (array_findstruct5,array_getallmatchingstructs6) against Champollion/UESP.string_index→.get(idx)/BadStringIndex(neverself.strings[idx]);value()accepts tags 0..=5,Integersign-reinterprets; endianness from magic (0xFA57C0DELE /0xDEC057FABE) with the provisional LE innew()never leaking into a multibyte read; Skyrim-vs-FO4+ field gating (is_skyrim():const_flag,struct_infos, property-group/struct-order tables; Starfield-onlyguards) reads in FileReader order and the consume-and-discard skips match the writer's byte counts.read_binaryis all-or-Err(no partialPex);call_sitesstops with a diagnostic when its budget is exhausted rather than truncating silently. Output:/tmp/audit/papyrus/dim_1.md
Dimension 2: Decompiler — CFG & Opcode→Node Lift (copy-propagation is the AST-correctness core)
Paths: crates/pex/src/decompile/{cfg,lift,node}.rs
First step: cargo test -p byroredux-pex -- decompile::cfg decompile::lift
- Jump targets:
checked_targetaccepts0 <= ip+offset <= count(inclusive —countis the exit anchor); non-integer offset →BadJumpOffset, OOB →JumpOutOfRange, non-{ident,bool,int} condition →BadJumpCondition.CodeBlock::split(at)is never called withat == 0(underflow). jmpf/jmptpolarity:jmpfjumps when FALSE (true-edge = fall-through); a flip inverts everyIf(forward_jmpf_builds_an_if_diamond,forward_jmpf_with_unconditional_backedge_builds_a_loop, andbackward_jmpt_builds_a_loop_edge— a trueJmpTpin since #4476; it was a misnamedJmpFtest before).rebuild_expression: a temp-producing node folds into the single immediately-next live consumer (count_constant_id: 0 → advance, 1 → inline, >1 →ExpressionRebuildFailed); folding into a non-adjacent consumer reorders side effects. It runs over a linked live-index chain, not restart-at-0/Vec::remove(O(n²), #2024 —rebuild_expression_is_linear_up_to_the_wire_format_ceiling).is_finalvsis_temp_varasymmetry (::tempprefix,_varsuffix) is a deliberate Champollion port — unifying them is a regression.replace_constant_id'sdebug_assert!(slot.is_none())is debug-only: a release >1-match would silently drop the producer.- Depth is memoized on
Node(Node::depth,recompute_depth) and checked at every fold againstMAX_EXPR_DEPTH(= the papyrus parser constant), so repeated re-folds by the boolean/control-flow passes cannot reset the ledger (the SIGABRT class); guardsa_temp_chain_past_the_depth_cap_is_declined_not_aborted/..._inside_the_depth_cap_still_folds. Any new in-place tree mutation must callrecompute_depth. create_node:Castdowngrades toCopyonly when source isNone, same-typed identifiers, or::nonevar(case-insensitive) — a wrong downgrade turns a narrowing cast into an identity copy. Operand order forCallMethod/CallStatic/CallParent(result/object/method indices) is what recognizers key on (SetStage,GetStageDone); cross-check UESP.id_ofon a literal →ExpectedIdentifier, neverunwrap()outside a checked branch. Bodyless/native functions decompile to an empty body. Output:/tmp/audit/papyrus/dim_2.md
Dimension 3: Decompiler — Boolean, Control-Flow, Lower & the Corpus Instruments
Paths: crates/pex/src/decompile/{boolean,control_flow,lower,event_names,mod}.rs, crates/pex/examples/pex_corpus_smoke.rs
First step: cargo test -p byroredux-pex -- decompile::; with game data: cargo run --release -p byroredux-pex --example pex_corpus_smoke -- <Skyrim - Misc.bsa> <Fallout4 - Misc.ba2>
- Pass order is load-bearing:
lower::decompile_bodyruns cfg → lift →rebuild_boolean_operators→reconstruct→lower_body; the boolean pass precedes control-flow so&&/||chains collapse first. control_flow.rs: shape classification (While:last.next == current; If:last.next == exit; If/Else; jmpt inversion negates + swaps edges). A conditional-predecessorlast(||shape the boolean pass should have collapsed) fails closed (ControlFlowFailed, #1732;conditional_predecessor_fails_closed) — a fix that resumes past the block drops statements. The module doc's "advanced past" wording is stale (#4115 open, verified 2026-10-21).- Bookkept Champollion departures —
boolean.rs's two (plus a third, newer guard below).lower.rs'sis→ object-typedCastdeparture (#4477) is closed: opcode 36 lowers toExpr::Is(#544324f199587,an_is_opcode_lowers_to_an_is_expression). For theboolean.rstwo — adjudicate benign-or-bug, evidence = corpus rate + fidelity gate, not speculation: (1) no debug-line guard (a same-named temp on a fall-through edge could be falsely collapsed into a fabricated&&/||); (2) termination guard (only re-process on a real merge — infinite loop hangs the decompiler). A declined collapse resumesrebuildat the operand range's END, not its first block (#587732352592f— every nested body was re-walked once per enclosing level, O(2^depth): a 656-byte.pextook 6.6 s;deeply_nested_ifs_are_walked_once_not_exponentially).&&true edge falls through,||false edge; operand must recompute the same condition variable; prec&&=7/||=8;take_operandleaves no danglingNone. Private-edge guard (944d0299e): a fold deletes the operand block (and a non-exit rejoin), so it declines unlessincoming(operand) == 1and, for an interior rejoin,incoming(rejoin) == 2— a sharedreturnepilogue that an early-exitjmpalso targets left a dangling edge and failedreconstruct(the last two corpus decompile failures: FO4StimBoxScript, FO76Debug.TraceLog; corpus now 100% on all four games). The in-edge counts are built once and kept exact bylink/unlinkatcollapse's three CFG mutations (#587932352592f— a per-fold whole-CFG scan was O(B²);in_edge_counts_survive_and_or_and_epilogue_foldscompares them to a recount). Pinsor_whose_rejoin_is_a_shared_epilogue_is_left_uncollapsed,and_whose_rejoin_is_a_shared_epilogue_is_left_uncollapsed. A new fold that deletes a block, or a new CFG mutation that skipslink/unlink, is the regression. - Recursion:
MAX_REBUILD_DEPTH = 1024incontrol_flow.rs(boolean.rsderives it — not restated); both rebuilds returnRecursionLimit(rebuild_rejects_excessive_recursion_depthexists in both files). It andMAX_EXPR_DEPTH(256) compose additively (~1,280) and independent edits erode the margin (#4113 open, verified 2026-10-21) — check any change to either constant. lower.rstotality:lower_expr/lower_stmtnever panic on anyNodeKind; intentional lossy lowerings matter only if a recognizer keys on the lost info: statement-shaped node as sub-expression →NoneLit(should be unreachable),is→Cast,lower_binary_opdefault arm →Eq(a real unknown op silently becomes==).StructCreateis no longer lossy: it lowers toExpr::NewStruct(FO4new S, the shape the.pscparser now emits too). Locals:lower_local_declsprepends one bareVarDeclper non-temp local ahead of the body (0c6fe4d34— without it every Champollion fragment prologue read as an assignment to an undeclared name and the #5638 fragment guard declined it;user_locals_are_declared_ahead_of_the_body_and_temporaries_are_not). "Temp" is ChampollionisTempVar(::temp*not ending_var, or::nonevar) —lower.rskeeps a private, case-insensitive copy besidenode.rs's case-sensitiveis_temp_var; check the two still agree — and is NOT every::name: the compiler's::mangled_<name>_<n>user locals must be declared (#588032352592f, ~280 vanilla functions declined under the old::filter).- Script assembly: FO4+
struct_infoslower toScriptItem::Structitems first (members keep their stored defaults;pex_corpus_smoke's expected-item-count addsstruct_infos.len()); synthetic::variables dropped; variable and auto-property defaults lower toinitial_valueviadefault_literal(the backing variable's stored default); the object'sconditional/hidden/defaultuser flags (bit indices named by the file's own user-flag table, resolved ONCE perPexbyUserFlagMasks::from_pex— #5683d8b1d333c, the old per-variable has_user_flag rescan was O(F×V), ~18 s on an 852 KB hostile file,a_hostile_user_flag_table_decompiles_in_linear_time;flag_index < 32guards the shift on untrusted bytes) lower into the sameScriptFlagsthe.pscheader parses, a variable'sconditionalintois_conditional(#5398ce90b481c,object_user_flags_lower_into_script_flags; struct members through the same table,struct_member_user_flags_lower_into_variable_flags) and a property'sconst/mandatory/hidden/conditionalintoPropertyFlags(#5685f7afb0e07,property_user_flags_lower_into_property_flags) — from the property's user bits OR its auto backing variable's, plus the backing variable'sconst_flag→CONST(#5882958c26c4e: vanilla tables never nameconstandAuto Const/Auto Conditionalbits live on the backing variable, ChampollionwriteProperty; ~22.5k FO4 / 24.5k StarfieldAuto Constonce lowered bare). The backing variable is found via one per-object map, not a per-property scan (the hostile linear-time test covers 65,535 auto properties, #5878); script scope = the empty-named state's functions; every named state → aStateitem; exactly one (the first case-insensitiveis_auto_statematch againstauto_state_name) isis_auto(#4473,case_colliding_state_names_mark_exactly_one_auto) — keying scope on the auto match inverted 983 vanilla scripts (#4319;named_auto_state_stays_an_auto_state_and_the_empty_state_is_script_scope). Event iff (on-prefixed ANDis_event_name) OR::remote_-prefixed;EVENT_NAMESis a sorted lowercase union binary-searched (list_is_sorted_for_binary_search); a missing engine event demotes a handler to a function. The list is a documented superset of Champollion's frozenEventNames.hpp: 12 real engine events it omits (OnAttach,OnStoryClearLocation, ten Starfield) are hand-added in sorted position from a base-class corpus census (#44710611cd116;the_census_engine_events_classify_as_events, which also keeps vanilla typos likeoncelldetatchout) — an addition needs the same census evidence, not a guess. - The decompile-rate claim measures robustness, not fidelity.
pex_corpus_smokecountsErrand panics as failures, tallies afterdecompile_script, and exits non-zero on any decompileErr, panic or shape mismatch, not only parse failures (#5883958c26c4e) — verify that gate still holds. Its shape check must not share an implementation rule with the code under test — the #3017 check anddecompile_scriptshared the wrong auto-state rule and both agreed on 983 mis-assembled scripts. Fidelity evidence is the.psc-vs-.pexgate (owned by/audit-scriptingDim 1:recognizes_da10_and_reproduces_hand_builder+da10_pex_reproduces_hand_builder_byte_for_byte), not the corpus rate. Output:/tmp/audit/papyrus/dim_3.md
Dimension 4: .psc Lexer & Pratt Parser (untrusted: .psc files and console/debug text)
Paths: crates/papyrus/src/{token,lexer,ast,error,lib}.rs, crates/papyrus/src/parser/{mod,expr,stmt,script}.rs
First step: cargo test -p byroredux-papyrus -- depth chain the_two_parser_depth_caps_stay_equal
- Depth caps (stack overflow and AST
Droprecursion):MAX_EXPR_DEPTH = 256(expr.rs) andMAX_STMT_DEPTH = 256(stmt.rs), bothpub, equality pinned bythe_two_parser_depth_caps_stay_equal; downstreamMAX_CONDITIONAL_DEPTHandlift.rs's cap alias them. The Pratt loop chargesexpr_depthfor each node a postfix/binary chain wraps (#4320 — the cap once counted only recursive calls, soa.a.a…of ~0.4 MB overflowed on Drop) and restores on every exit including error paths (long_postfix_and_binary_chains_hit_the_depth_cap,realistic_chains_still_parse_and_release_their_depth,depth_resets_between_top_level_calls). All recursive expression entry funnels throughparse_expr_bp. - Newline is a terminator in the Pratt loop: the loop uses
peek_raw(), not the newline-skippingpeek()(#4321 — a line opening with(was glued to the previous statement, a silent wrong AST). Explicit\continuations are a logos skip rule inlexer.rs(#5881af4a68ad7— no pre-pass; the;comment callback owns a trailing\); an operator ending a line still continues. Verify every otherpeek()on a statement boundary: #4472 (six sites, viacheck_raw) and #4763 (seven more —Extends, variable/function/group flags, property and local initializers) moved construct-continuation decisions to the raw stream, each pinned by an*_on_the_next_line_*test inscript.rsplussingle_line_forms_of_the_4763_sites_still_parse. Remainingpeek()/check()sites should sit behind an explicitskip_newlines()or a bracket-bounded list.expect()/expect_ident()still skip newlines, so a keyword's mandatory operand goes throughexpect_same_line/expect_raw/expect_ident_raw(parser/mod.rs; #5021,3b7ffa670): declaration names, theExtendstarget, the(after a function name,StateafterAuto, the If/ElseIf/While condition start, andNew's type and[— #5331fd5aa4ead,parse_new_expr; the[is optional only for anObjecttype, where a same-linenew Sis FO4'sExpr::NewStruct, and every segment after a qualified name's:(parse_qualified_ident→expect_ident_raw, #58853081d23f2: ScriptName/Extends/Import/event sender/types;a_namespaced_parent_on_the_next_line_is_still_not_the_parent) — pinned bya_mandatory_operand_on_the_next_line_is_an_error,auto_on_its_own_line_does_not_make_an_auto_state,extends_with_its_target_on_the_next_line_sets_no_parent,single_line_forms_of_the_5021_sites_still_parse. Only closing keywords may keep the newline-skippingexpect; a new declaration keyword that uses it is the regression. - Statement termination (#5322
a5c3f2428):expect_eol(parser/mod.rs) used to accept ANY token after a statement, so a tail the Pratt loop stopped at re-parsed as a new statement (If f is Actorbecame a truthiness test plus a fabricatedVarDecl) with zero errors. It now records a recoveredUnexpectedTokenfor anything but Newline/EOF/DocComment and skips to line end viaskip_to_line_end, which leaves block terminators (EndIf,Else*,End*) unconsumed so a gluedx = 1 EndIfstill closes its block (glued_statement_tail_is_an_error_and_not_a_statement;EndStruct/EndGroupstay too, #54443efa1c024,glued_struct_and_group_terminators_stay_for_their_container);parse_expr(debug console, offline preflight) ends inrequire_input_end(parse_expr_rejects_trailing_tokens). FO4isis a keyword with anExpr::Istype-test node at cast precedence (test_is_type_test); downstream exhaustive matches treat it as transparent to the operand (the.pexside lowersOpCode::Isto the sameExpr::Is, #5443; the VM evaluates it as the operand's truthiness). - Doc comments and FO4 names (
e96d687ec): Papyrus puts an item's{…}doc on the line AFTER its header, sotake_trailing_doc(parser/mod.rs) consumes one right after each header'sexpect_eol(script header, property, group, struct + member, function/event; #5325 — a bodied function's doc once became its first "statement" and the function was dropped).expect_eolaccepts a{doc}terminator only when it ends the line (#58863081d23f2,a_same_line_doc_comment_cannot_share_its_line_with_the_next_item). There is no leading-doc fallback: a top-level{lead}is discarded, and inside State/Group/accessor containers it is a parse error (#5890d148cd27b,a_leading_doc_comment_attaches_to_nothing). Script name,Extendstarget andImportare qualified idents (DLC01:Foo); a remote eventEvent Sender.Event(…)(#5328) is normalised to the compiler'sSender_Eventspelling — what the.pexfrontend yields and the VM's remote matcher binds, while a consumer matching the script's ownOnDeathdoes not (#58873081d23f2,remote_event_handlers_parse_with_their_sender_in_the_name).Parentis a legal declaration /.membername (#5888,parent_is_a_legal_member_name; the F4SE corpus test'sKNOWN_GAPSis now empty). Pinsa_doc_comment_after_a_bodied_function_header_documents_it,namespaced_script_name_parent_and_import_keep_every_segment. - Precedence/associativity:
BinaryOp::precedenceOr=1, And=2, comparisons=3, Add/Sub/StrCat=4, Mul/Div/Mod=5, unary=6, cast=7/postfix=8 (PREC_*inexpr.rs); left-assoc hinges onop_prec <= min_bp → break. (Bethesda's inverted CTDA OR/AND precedence is a condition-evaluation concern in/audit-scriptingDim 3 —.pscoperators are standard.) - Line continuation (#5881): a
\followed by blanks / a;comment /\n,\r\nor lone\ris one skip rule, so spans index the caller's own text with no offset map (#5884af4a68ad7— preprocess/OffsetMap are gone;spans_after_a_continuation_are_original_offsets,ast_spans_stay_in_original_coordinates_after_a_continuation); a trailing\at EOF is emitted, not swallowed. Unterminated;/block comments,{docs and string literals areLexErrors, not a silent swallow to EOF (#5889af4a68ad7,unterminated_block_comment_string_and_doc_are_lex_errors). Keywords areignore(case)(logos 0.16 dropped ascii_case,15a6b1d2d— Unicode folding, which differs only for the few non-ASCII letters that fold onto ASCII, e.g. U+017F, U+212A, invalid Papyrus either way) and win over theIdentregex. CK flags matched contextually stay out of the lexer:Default(script header) andCollapsed(group) are identifiers checked in their flag position (#5398ce90b481c,default_and_collapsed_remain_identifiers) — expressions accept onlyToken::Ident, so promoting either to a keyword breaks every variable of that name. Lone CR is a line terminator and CRLF oneNewline(bare_cr_is_a_newline_and_crlf_is_one_newline, #4479 — a CR-only file once lexed to zero newlines); a;line comment likewise stops at the first\ror\n(line_comment_ends_at_a_bare_cr, #5019a75c7dc33— it once searched only\n, swallowing a CR-only file to EOF with zero errors);0xand exponent shapes route throughMalformedNumberto aLexError, not an adjacent token pair. - Recovery:
parse_scriptreturnsOk((Script, Vec<ParseError>))for partial success andErronly for fatal;skip_to_next_linealways consumes ≥1 token (no infinite loop); callers needing strict-fail checkresult.1.is_empty(). Nounwrap()onstr::parseof a malformed-but-lexable literal. Output:/tmp/audit/papyrus/dim_4.md
Phase 3: Merge
Combine /tmp/audit/papyrus/dim_*.md into docs/audits/AUDIT_PAPYRUS_<TODAY>.md: Executive Summary
(findings by severity; untrusted-input robustness verdict — can a hostile .pex/.psc panic, OOB, OOM,
or stack-overflow the cell loader / console — must be NO; decompile-rate verdict — re-measured, and what
it does and does not prove) · Decompiler Soundness Matrix (reader / cfg / lift+copy-prop / boolean /
control-flow / lower / .psc parser × bounds-safe, terminates, total, fidelity-tested; the three Champollion
departures adjudicated) · Findings (severity order, deduplicated). Cross-audit: recognizer/runtime →
/audit-scripting; .pex bytes reaching the extender preflight → /audit-scripting Dim 7.
Phase 4: Cleanup
rm -rf /tmp/audit/papyrus; tell the user the report is ready; suggest
/audit-publish docs/audits/AUDIT_PAPYRUS_<TODAY>.md (domain label scripting).
Du fichier au premier essai
Le téléchargement contient exactement le contenu affiché sur cette fiche. Cette copie peut différer de la dernière version du dépôt source.
Consulter la source et ses fichiers annexesCe téléchargement contient uniquement SKILL.md. Les scripts, références, modèles et dépendances éventuels ne sont pas inclus. Récupérez les fichiers nécessaires depuis la source en conservant leur arborescence avant de poursuivre.
1. Télécharger le fichier
Télécharger SKILL.mdDepuis la racine d’un projet de test, enregistrez le fichier sous .skill-downloads/papyrus-frontend-audit-mvj1ocs5/SKILL.md, ou utilisez la commande suivante. Ce dossier sert à examiner la skill avant son installation.
Commande pour macOS/Linux ou WSL : un shell POSIX et curl sont nécessaires. Sous Windows sans WSL, utilisez le bouton de téléchargement et créez le dossier manuellement.
mkdir -p '.skill-downloads/papyrus-frontend-audit-mvj1ocs5' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/papyrus-frontend-audit-mvj1ocs5/download' --output '.skill-downloads/papyrus-frontend-audit-mvj1ocs5/SKILL.md.part' && mv '.skill-downloads/papyrus-frontend-audit-mvj1ocs5/SKILL.md.part' '.skill-downloads/papyrus-frontend-audit-mvj1ocs5/SKILL.md'2. Vérifier les prérequis
Lisez SKILL.md avant de l’utiliser. Si le fichier cite des scripts ou services externes, examinez-les et préparez les accès nécessaires dans votre environnement de test.
Aucune section de prérequis reconnue dans ce fichier. Cela ne signifie pas qu’il est autonome : les bibliothèques, outils et accès peuvent être cités ailleurs dans le contenu ou dans le dépôt source.
Les plateformes de la fiche sont des indications du catalogue. Les chemins ci-dessous suivent la documentation des outils ; ils ne prouvent pas que cette skill y a été exécutée. Aucune version minimale de ces outils n’est attestée pour cette fiche.
3. Charger la skill
Le fichier ne fournit pas un en-tête name/description exploitable pour cette procédure native. Le téléchargement reste disponible pour lecture ; utilisez le mode manuel ci-dessous.
Mode manuel ou autre outil : joignez SKILL.md à une conversation, ou ouvrez-le dans un agent capable de lire les fichiers. Ajoutez les ressources annexes nécessaires. Il n’existe pas de dossier universel reconnu par tous les outils.
4. Faire un premier essai
Utilisez une entrée courte et fictive : un endpoint d’API, un petit document ou un brief d’interface. Dans une nouvelle conversation, joignez le fichier ou donnez son chemin à l’agent, puis adaptez cette demande :
Lis .skill-downloads/papyrus-frontend-audit-mvj1ocs5/SKILL.md et applique ses instructions à cet exemple : [décrire une entrée de test]. Indique les fichiers, dépendances ou accès qui manquent. Produis le résultat demandé par la skill sans inventer de données ni exécuter de script non examiné.Résultat attendu : une sortie correspondant aux instructions de la skill, ou une liste précise des prérequis manquants. Une simple confirmation de lecture ne valide pas le premier usage. Si une ressource externe est indisponible, ce parcours reste à tester.
Sécurité AMM DeFi
Securite
Checklist de sécurité pour les contrats Solidity AMM, pools de liquidité et flux de swap. Couvre réentrance, attaques par donation, manipulation d'oracles, etc.
Créateur de règles Semgrep
Securite
Crée des règles Semgrep personnalisées pour détecter des vulnérabilités de sécurité, des motifs de bugs et des motifs de code. Inclut une méthodologie de test en premier, le support du mode taint et des étapes de validation. Utile pour construire des détections d'analyse statique ciblées.
Revue barre d'outils développement
Securite
Règles de sécurité pour la barre d'outils de développement, le client PostHog et les feature flags. À utiliser lors de la revue de modifications des composants dev-tools et connexes.