Automated summary
Performs a structured security audit of the Papyrus .pex reader and decompiler and the .psc lexer/parser, focusing on untrusted-input bounds and silent wrong-AST risks.
Strengths
- Scopes the audit precisely to crates/pex and crates/papyrus with the relevant files and modules.
- Separates decompiler soundness defects from lower-impact documentation rot.
- Defines severity escalations for panic, out-of-bounds access, unbounded allocation or recursion, and wrong ASTs accepted by recognizers.
- Uses a delta-first workflow with guard re-verification and targeted test execution.
Limitations
- Tightly coupled to the ByroRedux repository, its paths, audit documents, and internal conventions.
- Does not cover recognizers, runtime behavior, or adjacent parsers such as hkx and ESM VMAD.
- Assumes access to GitHub issue history, corpus tooling, and project-specific instrumentation.
Use it when auditing ByroRedux's Papyrus frontends for security or robustness against untrusted .pex/.psc inputs.
Avoid it for a whole-pipeline scripting audit, other file formats, or projects lacking the referenced ByroRedux crates and documents.
Security analysis
CautionNo exfiltration, obfuscation, or curl|sh-style payloads. The skill is a read-only analysis workflow: it runs the project's own tests/examples, reads git history and GitHub issues, writes a report under docs/audits, and cleans up its own /tmp scratch dir. Power is used legitimately (build/test tooling, network read via gh, a fixed-path rm -rf), so this lands at 'caution' rather than 'safe' or 'risky'.
- •Skill assumes shell/CLI execution (cargo test, cargo run --example, git log, gh issue list) despite declaring no allowed-tools, so the required privilege set is not made explicit.
- •Phase 4 runs `rm -rf /tmp/audit/papyrus` — destructive, but scoped to a fixed temp path the skill itself creates via `mkdir -p`; no variable substitution or wildcard, so blast radius is minimal.
- •`gh issue list --repo ...` performs outbound network calls to GitHub and relies on the user's stored gh credentials (token read implicitly by the gh CLI, not handled or exfiltrated by the skill).
- •`cargo run --release -p byroredux-pex --example pex_corpus_smoke` processes untrusted .pex/.psc corpora; the skill's whole purpose is hardening against hostile input, and it explicitly treats panics/OOM as findings, which is defensive rather than risky.
- •Uses shell tooling (mkdir, git, gh, cargo test/run, rm) throughout — legitimate for an audit workflow but requires bash and network access (gh issue list, cargo) to execute as written
- •Phase 4 runs `rm -rf /tmp/audit/papyrus`; the target is the skill's own scratch directory created in Phase 1, so the blast radius is confined, but blanket `rm -rf` is worth flagging for a reviewer
- •Runs example binaries compiled from the repo (`pex_corpus_smoke`, `r5_extract_pex_ba2`) against game-archive inputs — expected for the task, no exfiltration or obfuscation
- •No instructions embedded in the content attempt to alter the auditor's role or assessment; treated the whole document as data
Examples
/audit-papyrus --focus pex-reader --depth deepAudit the Papyrus decompiler passes for cases where copy propagation or boolean collapse could produce a wrong AST that a recognizer accepts, and report HIGH findings with file and line evidence./audit-papyrus --focus papyrus-lexer-parser --depth shallow — check .psc lexer and Pratt parser depth caps, allocation bounds, and panic paths reachable from untrusted source.description: "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-15).- Three bookkept Champollion departures —
boolean.rs's two (plus a third, newer guard below), andlower.rs'sis→ object-typedCast(#4477,an_is_opcode_lowers_to_an_object_typed_cast; recognizer-irrelevant only while no consumer keys on it). 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).&&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 now 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). 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 without an in-edge count 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-15) — 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-::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).- 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) and a property'sconst/mandatory/hidden/conditionalintoPropertyFlags(#5685f7afb0e07,property_user_flags_lower_into_property_flags); 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. Verify
pex_corpus_smokecountsErrand panics as failures and tallies afterdecompile_script. 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 joined bylexer::preprocess; 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— 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 still lowersOpCode::Isto an object-typedCast, so the two frontends do NOT yet share one shape). - 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). Script name,Extendstarget andImportare qualified idents (DLC01:Foo), and an event name may be the remote formSender.Eventkept dotted in the name so a name-matching consumer declines it (#5328). 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.) preprocess:\+\n/\r\n/lone\relided (2/3/2 bytes) with exactOffsetMapcounts (a wrong count drifts every later span); a trailing\at EOF is emitted, not swallowed. 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).
From the file to a first result
The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.
View the source and supporting filesThis download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.
1. Download the file
Download SKILL.mdFrom the root of a test project, save the file as .skill-downloads/papyrus-frontend-audit-pex-psc-mvda88sk/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.
Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.
mkdir -p '.skill-downloads/papyrus-frontend-audit-pex-psc-mvda88sk' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/papyrus-frontend-audit-pex-psc-mvda88sk/download' --output '.skill-downloads/papyrus-frontend-audit-pex-psc-mvda88sk/SKILL.md.part' && mv '.skill-downloads/papyrus-frontend-audit-pex-psc-mvda88sk/SKILL.md.part' '.skill-downloads/papyrus-frontend-audit-pex-psc-mvda88sk/SKILL.md'2. Check prerequisites
Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.
No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.
The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.
3. Load the skill
This file does not provide a usable name/description header for this native procedure. It remains available to download and inspect; use the manual method below.
Manual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.
4. Try a first task
Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:
Read .skill-downloads/papyrus-frontend-audit-pex-psc-mvda88sk/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.
DeFi AMM Security
Security
Security checklist for Solidity AMM contracts, liquidity pools, and swap flows. Covers reentrancy, donation attacks, oracle manipulation, slippage, and integer math.
Semgrep Rule Creator
Security
Creates custom Semgrep rules for detecting security vulnerabilities, bug patterns, and code patterns. Includes test-first methodology, taint mode support, and validation steps. Helps when building targeted static analysis detections.
Dev Toolbar Review Guide
Security
Safety rules for the dev toolbar, PostHog client, and feature flags. Use when reviewing changes to dev-tools and related files, covering environment guards, override cookies, and SSE stream safety.