Files
papercraft/tests/update_security.rs
Marcos Paulo 567ee63c7a feat: export sites, translate pages, and harden agent tools and sync
New agent tools (98 to 104):
- export_site writes a website from frames in one call: plain HTML,
  Astro, Vue (Nuxt or Vite), React (Next or Vite) or Angular; css, scss
  or tailwind, and for React vanilla-extract, styled-components or
  emotion; static pages or a single-page app; a folder per language.
  Fonts are subset to WOFF2 with metric-matched fallbacks, images are
  resized, headings follow the outline, pages reflow below the designed
  width, and _headers, vercel.json, _redirects and .nojekyll cache
  hashed assets for a year.
- get_strings and set_strings treat languages as variable modes: read a
  page's text for translation, fill a language or a pseudo-locale.
- get_context says where the agent is, what is cheap to read next and
  what lint found, in one call; big reads take maxTokens and report
  estimatedTokens.
- import_html opens a page from a prompt-to-design tool (.html or .zip)
  as layers.
- install_font adds fonts on top of the bundled Inter; text can name one.

App:
- Present mode plays a page's frames as a prototype: links jump to
  frames, open overlays, go back or open an address.
- Panics are kept in crash.log; release builds keep line tables so its
  traces name files and lines.
- Settings > Canvas is kept between sessions; a keyboard shortcuts
  sheet; icons in the menus.
- Chat: an opencode turn waiting out a rate limit moves on to the
  fallback model.
- The updater checks each download against the SHA-256 GitHub publishes.

Fixes:
- A NaN or infinite tool argument no longer makes a project unopenable.
- Agent paths cannot reach other machines, devices or NTFS streams; a
  tool writes only its own file type, and fills only a new, empty or
  earlier export folder.
- delegate and imagegen pass each value in its own environment
  variable, never as shell syntax.
- An instance placed inside its own component is refused.
- A panic in a tool fails that call instead of closing the app.
- An agent call during a drag waits for it, so the undo step stays whole.
- Sync holds downloads while the open project has unsaved edits, writes
  pages atomically, and leaves a page saved while its download was in
  flight for the next round to merge. Merges repair the tree instead
  of giving a node two parents or orphaning it.
- Share tokens come from the OS random generator.
- Imports have a total memory cap; Excalidraw import is linear.
- Unused images are freed when a project opens.
- Pages that use no added font are still written as format 3, so 0.9.1
  can open them.
- Code export escapes text, attributes and CSS in one place.

CI: a clippy job (warnings do not fail it yet).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J9PiVCQxc2QYmArFq9VBUX
2026-11-06 10:29:43 -03:00

65 lines
2.2 KiB
Rust

use papercraft::update::{Asset, parse_release, validate_asset};
use serde_json::json;
fn feed(url: &str, tag: &str) -> String {
json!({"tag_name":tag,"html_url":url,"assets":[]}).to_string()
}
#[test]
fn similarly_named_repository_is_not_trusted() {
let json = feed(
"https://git.marcospaulo.dev.br/masi/papercraft-untrusted/releases/tag/v0.4.0",
"v0.4.0",
);
assert!(parse_release(&json, "0.3.1", "macos", "aarch64").is_err());
}
#[test]
fn normalized_download_paths_must_stay_in_official_repository() {
let asset = Asset {
name: "papercraft-0.4.0-macos-aarch64.dmg".into(),
url: "https://git.marcospaulo.dev.br/masi/papercraft/releases/download/v0.4.0/%2e%2e/%2e%2e/%2e%2e/%2e%2e/other/papercraft-0.4.0-macos-aarch64.dmg".into(),
size: 1024,
digest: "a".repeat(64),
};
assert!(validate_asset(&asset).is_err());
}
#[test]
fn prerelease_tag_is_refused_even_if_feed_flag_is_missing() {
let json = feed(
"https://git.marcospaulo.dev.br/masi/papercraft/releases/tag/v0.4.0-beta.1",
"v0.4.0-beta.1",
);
assert!(parse_release(&json, "0.3.1", "macos", "aarch64").is_err());
}
#[tokio::test]
#[ignore = "downloads an installer from the official release server"]
async fn official_release_can_be_checked_and_downloaded() {
use papercraft::update::{RELEASES_URL, check, download};
check().await.expect("official update check succeeds");
let response = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(30))
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap()
.get(RELEASES_URL)
.send()
.await
.unwrap()
.error_for_status()
.unwrap();
let json = response.text().await.unwrap();
let release = parse_release(&json, "0.0.0", std::env::consts::OS, std::env::consts::ARCH)
.unwrap()
.expect("published release exists");
let asset = release
.asset
.expect("native installer published for this machine");
let path = download(&asset, |_, _| {})
.await
.expect("installer downloads completely");
assert_eq!(std::fs::metadata(path).unwrap().len(), asset.size);
}