Sikre eksporter med custom templates og fonts

To extension points med trust-krav

bfh_export_pdf() og bfh_create_export_session() eksponerer to parametre der kører caller-supplied kode/templates. Begge er legitime for proprietære fonts og organisationsspecifikke templates, men begge kræver eksplicit trust:

Parameter Hvad det gør Risiko ved misbrug
inject_assets Kalder en R-callback med skrive-adgang til template-mappen Vilkårlig R-kode-eksekvering
template_path Erstatter den pakkede Typst-template Vilkårlig Typst (kan læse/skrive paths under compilation)

Begge parametre skal behandles med samme tillidskontrakt som source(): aldrig fra user-input, altid fra code-reviewed organisationskontrolleret kilde.

Brug font_path for de fleste tilfælde

Hvis du kun har brug for at injicere fonts, brug font_path i stedet for inject_assets. Det er sikkert at videresende fra konfiguration eller miljøvariable, og kræver ikke trust på samme niveau:

library(BFHcharts)

# Fonts er statiske filer på disk — ingen kode-eksekvering
bfh_export_pdf(result, "rapport.pdf",
  font_path = "/etc/bfh-fonts"
)

font_path videregives som --font-path til Typst-compileren. Typst læser fonts fra mappen men eksekverer ingen kode fra den.

inject_assets — kun til organisations-kontrolleret kode

Brug inject_assets når du har dynamiske filsystem-operationer der ikke passer ind i en fast font_path-mappe. Eksempel: en privat pakke der bundler fonts og afpakker dem til template-mappen lige før compilation.

# Sikkert: callback fra code-reviewed privat pakke
inject_my_org_assets <- function(template_dir) {
  font_dir <- file.path(template_dir, "fonts")
  dir.create(font_dir, showWarnings = FALSE)
  file.copy(
    system.file("fonts", "Mari", package = "MyOrgPrivateFonts"),
    font_dir,
    recursive = TRUE
  )
}

bfh_export_pdf(result, "rapport.pdf",
  inject_assets = inject_my_org_assets
)
# FARLIGT: callback bygget fra Shiny-input
server <- function(input, output, session) {
  output$pdf <- downloadHandler(
    content = function(file) {
      # ALDRIG videresend user-supplied kode/strenge til inject_assets:
      user_callback <- parse_user_input(input$user_callback)
      bfh_export_pdf(result, file, inject_assets = user_callback)
    }
  )
}

Sidstnævnte mønster konverterer en Shiny-input til en eksekverbar callback — hvilket giver brugeren vilkårlig kode-eksekvering på serveren. Kald den “remote code execution” eller “RCE”: uacceptabel selv i interne enterprise deployments.

template_path — kun til organisations-kontrolleret Typst

Tilsvarende: template_path peger på en .typ-fil som Typst-compileren kører. En custom template kan udføre filsystem-operationer under compilation — så aldrig modtag template-stien fra user-input.

Siden BFHcharts 0.16.0 kræver custom-template eksplicit opt-out via restrict_template = FALSE. Default er TRUE (production-safe). Callers der ikke eksplicit opt-outer modtager en valideringsfejl ved kald-tid.

# Sikkert: template fra organisations-repo, code-reviewed
bfh_export_pdf(result, "rapport.pdf",
  template_path = system.file(
    "templates", "afdelings-template.typ",
    package = "MyOrgTemplates"
  ),
  restrict_template = FALSE  # eksplicit opt-out paakraevet siden 0.16.0
)
# FARLIGT: template-sti fra user upload
template <- input$uploaded_template$datapath
bfh_export_pdf(result, "rapport.pdf",
  template_path = template
)

Den uploadede .typ-fil eksekveres af Typst-compileren med samme privilege som R-processen. Vilkårlig Typst-kode kan læse og skrive filsystem-paths.

Allow-list pattern: hvis du skal eksponere customisering

Hvis dit Shiny-interface skal lade brugere vælge mellem forhåndsgodkendte templates, validér mod en fixed allow-list før kald:

APPROVED_TEMPLATES <- list(
  standard = system.file("templates/standard.typ", package = "MyOrg"),
  klinisk  = system.file("templates/klinisk.typ", package = "MyOrg"),
  forskning = system.file("templates/forskning.typ", package = "MyOrg")
)

server <- function(input, output, session) {
  output$pdf <- downloadHandler(
    content = function(file) {
      # User vælger fra dropdown med navn — ikke fil-sti
      template <- APPROVED_TEMPLATES[[input$template_name]]
      if (is.null(template) || !file.exists(template)) {
        stop("Ugyldig template valgt")
      }
      bfh_export_pdf(result, file,
                     template_path = template,
                     restrict_template = FALSE)
    }
  )
}

UI komponenten viser kun navnene fra allow-listen — fil-stier eksponeres aldrig.

Batch eksport med bfh_create_export_session()

For mange eksporter med identiske template-assets: brug session for at undgå gentaget I/O. Trust-kravet for inject_assets er det samme:

session <- bfh_create_export_session(
  font_path = "/etc/bfh-fonts",
  inject_assets = inject_my_org_assets   # Code-reviewed callback
)
on.exit(close(session))

for (afdeling in afdelinger) {
  result <- bfh_qic(data_per_afd[[afdeling]], ...)
  bfh_export_pdf(result, paste0(afdeling, ".pdf"),
    batch_session = session,
    metadata = list(department = afdeling)
  )
}

inject_assets kaldes én gang ved session-opret; assets deles på tværs af alle eksporter. Trust-kravet er det samme som single-call mode.

Font-fallback chain

Hvis font_path ikke matcher Typst’s font-lookup, falder den tilbage på system-fonts (medmindre ignore_system_fonts = TRUE, default fra v0.10.0). Anbefalet praksis i production:

bfh_export_pdf(result, "rapport.pdf",
  font_path = "/etc/bfh-fonts",
  ignore_system_fonts = TRUE
)

Med ignore_system_fonts = TRUE bruger Typst kun fonts fra font_path plus de bundle’de template-fonts. Det forhindrer overraskende rendering-forskelle mellem dev-maskiner og production-deployments hvor system-font-versioner divergerer.

Pre-deploy security checklist

Før dit Shiny app eller batch-script går i produktion:

AI-egress audit signal

Som defense-in-depth emitter bfh_generate_analysis(use_ai = TRUE) en message() umiddelbart inden kald til BFHllm::bfhllm_spc_suggestion(). Beskeden bærer det stabile tag [BFHcharts/AI] og navngiver de felter der transmitteres, så R-level logs kan bekræfte om og hvornår AI-stien blev taget. Sæt options(BFHcharts.suppress_ai_audit_message = TRUE) for at undertrykke beskeden (f.eks. i applikationer der har egen audit-trail).

Se også

  • vignette("chart-types") — vælg charttype før eksport
  • ?bfh_export_pdf\section{Security} med fuld trust-rationale
  • ?bfh_create_export_session — batch-session security note
  • BFHcharts issue #218 — security boundary discussion