From 3bbbf061b34a142c228617e9d7cceaa9c7e656f3 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 26 May 2026 17:37:30 +0100 Subject: [PATCH 1/2] security(audits): assail-classifications for 8 of 9 panic-attack #378 findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit panic-attack estate sweep flagged 9 Critical/High findings in this repo (issue #378). After per-finding triage, 8 of 9 are false positives or vendored-subtree concerns. The remaining 1 (affinescriptiser/flake.nix) was an unfilled scaffold leftover with {{PROJECT_NAME}} placeholders — deleted in the next commit rather than suppressed. Classifications added: UnboundedAllocation (4 sites) — all `std::fs::read_to_string` calls reading local user-authored TOML config/manifest/lockfile/source-file paths. Trust model is identical to rustc/cargo: the user is the operator; an attacker who can write multi-GB files to the user's ~/.config/ is already past the security boundary. Sites: - affinescriptiser/src/codegen/parser.rs (source path) - tools/affine-pkg/src/lockfile.rs (lockfile) - tools/affine-pkg/src/manifest.rs (manifest) - tools/affine-pkg/src/config.rs (global + project config) DynamicCodeExecution (2 sites): - tools/affine-doc/assets/search.js — pattern-matched a COMMENT explaining the code AVOIDS innerHTML. Render path uses .textContent throughout. Confirmed by reading the file. - road-skate/game/main.js — vendored subtree; fix in upstream hyperpolymath/road-skate, not this tree. SupplyChain (2 sites): - road-skate/flake.nix — vendored subtree. - affinescript-vite/flake.nix — vendored subtree. Schema cribbed from hyperpolymath/echidna/audits/assail-classifications.a2ml. Refs hyperpolymath/affinescript#378 Refs hyperpolymath/panic-attack#32 Co-Authored-By: Claude Opus 4.7 (1M context) --- audits/assail-classifications.a2ml | 82 ++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 audits/assail-classifications.a2ml diff --git a/audits/assail-classifications.a2ml b/audits/assail-classifications.a2ml new file mode 100644 index 00000000..d26f4438 --- /dev/null +++ b/audits/assail-classifications.a2ml @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Classification registry for panic-attack assail findings that are +# confirmed false positives (pattern-match against local-trust files or +# vendored subtrees) and should not be treated as actionable weak-points +# in this repo's tree. +# +# Format mirrors panic-attack's user-classification registry protocol +# (cf. hyperpolymath/echidna/audits/assail-classifications.a2ml). +# +# Cross-reference: issue #378 (panic-attack estate sweep, 2026-05-26). +# Estate tracker: hyperpolymath/panic-attack#32. + +[metadata] +name = "affinescript-assail-classifications" +project = "affinescript" +version = "1.0.0" +schema_version = "1.0.0" + +(assail-classifications + + ; ─── UnboundedAllocation (Critical) ──────────────────────────────────── + ; + ; All four flagged sites are `std::fs::read_to_string` calls reading + ; local user-authored TOML files (config, manifest, lockfile) or a + ; user-supplied source-file path. The trust model for these is + ; identical to rustc / cargo / any compiler reading user-supplied + ; source: the user is the operator; an attacker who can write a + ; multi-GB file into the user's `~/.config/affine/` or pass arbitrary + ; paths via the CLI is already past the security boundary. Not an + ; attacker-controlled-size allocation in the OWASP sense. + + (classification + (file "affinescriptiser/src/codegen/parser.rs") + (category "UnboundedAllocation") + (rationale "read_to_string(&source.path) — local source file path; rustc/cargo trust model")) + + (classification + (file "tools/affine-pkg/src/lockfile.rs") + (category "UnboundedAllocation") + (rationale "read_to_string(path) — local lockfile; cargo-equivalent trust model")) + + (classification + (file "tools/affine-pkg/src/manifest.rs") + (category "UnboundedAllocation") + (rationale "read_to_string(path) — local Cargo.toml-equivalent manifest; user-authored")) + + (classification + (file "tools/affine-pkg/src/config.rs") + (category "UnboundedAllocation") + (rationale "read_to_string(&path) for global + project-local TOML config; user-authored")) + + ; ─── DynamicCodeExecution (High) ──────────────────────────────────────── + + (classification + (file "tools/affine-doc/assets/search.js") + (category "DynamicCodeExecution") + (rationale "Pattern-match against a COMMENT explaining the code avoids innerHTML. Actual render path uses .textContent throughout — search.js line 7-9: 'HTML escape function to prevent XSS. Uses character substitution — no DOM element, no innerHTML write.' No actual innerHTML/document.write writes exist in the file.")) + + (classification + (file "road-skate/game/main.js") + (category "DynamicCodeExecution") + (rationale "Vendored from upstream hyperpolymath/road-skate. Fix in upstream repo, not here. (Path-based exclusion for vendored subtree would be cleaner once panic-attack supports it; see panic-attack#32.)")) + + ; ─── SupplyChain (High) — vendored subtrees ───────────────────────────── + ; + ; The two flake.nix files below live in vendored subtrees with their + ; own upstream repos. The affinescriptiser/flake.nix that panic-attack + ; ALSO flagged was an unfilled scaffold leftover (had {{PROJECT_NAME}} + ; placeholders) — deleted in this PR rather than suppressed. + + (classification + (file "road-skate/flake.nix") + (category "SupplyChain") + (rationale "Vendored from upstream hyperpolymath/road-skate. Input pinning fix belongs upstream.")) + + (classification + (file "affinescript-vite/flake.nix") + (category "SupplyChain") + (rationale "Vendored from upstream hyperpolymath/affinescript-vite. Input pinning fix belongs upstream.")) +) From 6f5c2496dfd8b71264b2535ec92dc7379b9e7e45 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 26 May 2026 17:38:33 +0100 Subject: [PATCH 2/2] chore(affinescriptiser): delete unfilled flake.nix scaffold leftover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The file had {{PROJECT_NAME}}, {{CURRENT_YEAR}}, {{AUTHOR}}, etc. placeholders throughout — never instantiated from the project-scaffold template. As-is it isn't a usable Nix flake (nix can't parse the placeholders). Not referenced by any tool: `grep -rln "affinescriptiser/flake"` across .sh / .yml / .adoc / .md / .toml / .just returns empty. No parent flake.nix at the affinescript root either (this is an OCaml + AffineScript primary repo; Nix is fallback only at the sub-package level, and the parent has guix.scm as the primary dev env). Deleting honestly disposes of the unfilled template rather than suppressing the panic-attack SupplyChain finding (unpinned inputs) which is technically correct but moot for an un-parseable file. If affinescriptiser ever needs a real flake.nix later, instantiate from a fresh template with real values + input pinning + flake.lock. This is the third commit of the #378 triage (one suppression file + this delete; the other 8 findings are FPs covered in the suppression file). Refs hyperpolymath/affinescript#378 Co-Authored-By: Claude Opus 4.7 (1M context) --- affinescriptiser/flake.nix | 170 ------------------------------------- 1 file changed, 170 deletions(-) delete mode 100644 affinescriptiser/flake.nix diff --git a/affinescriptiser/flake.nix b/affinescriptiser/flake.nix deleted file mode 100644 index df7a0a59..00000000 --- a/affinescriptiser/flake.nix +++ /dev/null @@ -1,170 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) {{CURRENT_YEAR}} {{AUTHOR}} ({{OWNER}}) <{{AUTHOR_EMAIL}}> -# -# Nix flake for {{PROJECT_NAME}} -# -# NOTE: guix.scm is the PRIMARY development environment. This flake is provided -# as a FALLBACK for contributors who use Nix instead of Guix. The .envrc checks -# for Guix first, then falls back to Nix. -# -# Usage: -# nix develop # Enter development shell -# nix build # Build the project -# nix flake check # Run checks -# nix flake show # Show flake outputs -# -# With direnv (.envrc already configured): -# direnv allow # Auto-enters shell on cd -# -# TODO: Replace {{PROJECT_NAME}} and {{PROJECT_DESCRIPTION}} with actual values. - -{ - description = "{{PROJECT_NAME}} — RSR-compliant project"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - flake-utils.url = "github:numtide/flake-utils"; - }; - - outputs = { self, nixpkgs, flake-utils }: - flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system: - let - pkgs = import nixpkgs { inherit system; }; - - # Common development tools present in every RSR project. - commonTools = with pkgs; [ - git - just - nickel - curl - bash - coreutils - ]; - - # --------------------------------------------------------------- - # Language-specific packages: uncomment the stacks you need. - # --------------------------------------------------------------- - # - # Rust: - # rustc cargo clippy rustfmt rust-analyzer - # - # Elixir: - # elixir erlang - # - # Gleam: - # gleam erlang - # - # Zig: - # zig zls - # - # Haskell: - # ghc cabal-install haskell-language-server - # - # Idris2: - # idris2 - # - # OCaml: - # ocaml dune_3 ocaml-lsp - # - # ReScript (via Deno): - # deno - # - # Julia: - # julia - # - # Ada/SPARK: - # gnat gprbuild - # - # --------------------------------------------------------------- - languageTools = with pkgs; [ - # TODO: Uncomment or add packages for your stack. - # Example for a Rust project: - # rustc - # cargo - # clippy - # rustfmt - # rust-analyzer - ]; - - in - { - # --------------------------------------------------------------- - # Development shell — `nix develop` - # --------------------------------------------------------------- - devShells.default = pkgs.mkShell { - name = "{{PROJECT_NAME}}-dev"; - - buildInputs = commonTools ++ languageTools; - - # Environment variables available inside the shell. - env = { - PROJECT_NAME = "{{PROJECT_NAME}}"; - RSR_TIER = "infrastructure"; - }; - - shellHook = '' - echo "" - echo " {{PROJECT_NAME}} — development shell" - echo " Nix: $(nix --version 2>/dev/null || echo 'unknown')" - echo " Just: $(just --version 2>/dev/null || echo 'not found')" - echo "" - echo " Run 'just' to see available recipes." - echo "" - - # Source .envrc manually when direnv is not managing the shell. - # This keeps project env vars (PROJECT_NAME, DATABASE_URL, etc.) - # consistent whether you enter via 'nix develop' or 'direnv allow'. - if [ -z "''${DIRENV_IN_ENVRC:-}" ] && [ -f .envrc ]; then - # Only source the non-nix parts to avoid recursion. - export PROJECT_NAME="{{PROJECT_NAME}}" - export RSR_TIER="infrastructure" - if [ -f .env ]; then - set -a - . .env - set +a - fi - fi - ''; - }; - - # --------------------------------------------------------------- - # Package — `nix build` - # --------------------------------------------------------------- - packages.default = pkgs.stdenv.mkDerivation { - pname = "{{PROJECT_NAME}}"; - version = "0.1.0"; - - src = self; - - # TODO: Replace with real build instructions. - # Examples: - # - # Rust (use rustPlatform.buildRustPackage instead of stdenv): - # packages.default = pkgs.rustPlatform.buildRustPackage { ... }; - # - # Elixir (use mixRelease): - # packages.default = pkgs.beamPackages.mixRelease { ... }; - # - # Zig: - # buildPhase = "zig build -Doptimize=ReleaseSafe"; - - buildPhase = '' - echo "TODO: Add build commands for {{PROJECT_NAME}}" - ''; - - installPhase = '' - mkdir -p $out/share/doc - cp README.adoc $out/share/doc/ 2>/dev/null || true - ''; - - meta = with pkgs.lib; { - description = "{{PROJECT_DESCRIPTION}}"; - homepage = "https://github.com/{{OWNER}}/{{PROJECT_NAME}}"; - license = licenses.mpl20; # MPL-2.0 extends MPL-2.0 - maintainers = []; - platforms = [ "x86_64-linux" "aarch64-linux" ]; - }; - }; - } - ); -}