///|
/// Named policy template for examples and quick starts.
pub(all) struct PolicyTemplate {
name : String
title : String
use_case : String
header : String
notes : String
} derive(Eq, Debug)
///|
fn make_policy_template(
name : String,
title : String,
use_case : String,
header : String,
notes : String,
) -> PolicyTemplate {
{ name, title, use_case, header, notes }
}
///|
pub fn PolicyTemplate::parse(
self : PolicyTemplate,
) -> Result[Policy, ParseError] {
parse_policy(self.header[:])
}
///|
pub fn PolicyTemplate::score(self : PolicyTemplate) -> PolicyScore? {
match self.parse() {
Ok(policy) => Some(policy.score())
Err(_) => None
}
}
///|
pub fn PolicyTemplate::summary(self : PolicyTemplate) -> String {
self.name + " " + self.title + " " + self.use_case
}
///|
pub fn PolicyTemplate::audit_text_report(self : PolicyTemplate) -> String {
match self.parse() {
Ok(policy) => self.summary() + "\n" + policy.audit_text_report()
Err(_) => self.summary() + "\ninvalid template"
}
}
///|
pub fn strict_static_site_template() -> PolicyTemplate {
make_policy_template(
"strict-static-site", "Strict static site", "HTML generated site with local scripts and images.",
"default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests",
"Good baseline for documentation sites that do not use third-party script.",
)
}
///|
pub fn nonce_spa_template() -> PolicyTemplate {
make_policy_template(
"nonce-spa", "Nonce based single page application", "Frontend app with a server generated nonce.",
"default-src 'self'; script-src 'self' 'nonce-demo' 'strict-dynamic'; style-src 'self'; img-src 'self' https: data:; connect-src 'self' https:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests",
"Replace nonce-demo with a per-response unpredictable nonce.",
)
}
///|
pub fn wasm_app_template() -> PolicyTemplate {
make_policy_template(
"wasm-app", "WebAssembly application", "MoonBit WebAssembly app with controlled dynamic compilation needs.",
"default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self' https:; img-src 'self' data:; style-src 'self'; worker-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests",
"Use wasm-unsafe-eval only when the runtime really requires dynamic wasm compilation.",
)
}
///|
pub fn api_console_template() -> PolicyTemplate {
make_policy_template(
"api-console", "API console", "Interactive API documentation with remote API endpoints.",
"default-src 'self'; script-src 'self' 'nonce-demo'; style-src 'self'; img-src 'self' data: https:; connect-src 'self' https://api.service.local; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'; report-to csp-endpoint; upgrade-insecure-requests",
"Keep connect-src endpoints explicit for each deployment.",
)
}
///|
pub fn admin_panel_template() -> PolicyTemplate {
make_policy_template(
"admin-panel", "Admin panel", "Sensitive business dashboard with no embedding.",
"default-src 'self'; script-src 'self' 'nonce-demo'; style-src 'self'; img-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'; require-trusted-types-for 'script'; trusted-types default; upgrade-insecure-requests",
"A strict baseline for pages with sensitive administrative actions.",
)
}
///|
pub fn markdown_preview_template() -> PolicyTemplate {
make_policy_template(
"markdown-preview", "Markdown preview", "Preview surface that renders user content.",
"default-src 'none'; img-src 'self' https: data:; style-src 'self'; script-src 'self' 'nonce-demo'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'none'; sandbox allow-scripts allow-same-origin",
"Rendering user content should prefer a sandboxed origin.",
)
}
///|
pub fn embedded_widget_template() -> PolicyTemplate {
make_policy_template(
"embedded-widget", "Embedded widget", "Widget intentionally loaded inside partner sites.",
"default-src 'self'; script-src 'self' 'nonce-demo'; style-src 'self'; img-src 'self' https: data:; connect-src 'self' https:; object-src 'none'; base-uri 'none'; frame-ancestors https://partner.service.local; form-action 'none'; upgrade-insecure-requests",
"frame-ancestors should list only approved embedding origins.",
)
}
///|
pub fn locked_down_document_template() -> PolicyTemplate {
make_policy_template(
"locked-down-document", "Locked down document", "Read-only document with no script execution.",
"default-src 'none'; img-src 'self' data:; style-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'none'; sandbox",
"Useful for generated reports or static pages that do not need script.",
)
}
///|
pub fn report_only_migration_template() -> PolicyTemplate {
make_policy_template(
"report-only-migration", "Report-only migration", "Migration stage where violations are observed before enforcement.",
"default-src 'self'; script-src 'self' 'nonce-demo'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; connect-src 'self' https:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; report-to csp-endpoint; upgrade-insecure-requests",
"Use as a starting point, then remove unsafe-inline after telemetry stabilizes.",
)
}
///|
pub fn legacy_cdn_template() -> PolicyTemplate {
make_policy_template(
"legacy-cdn", "Legacy CDN migration", "Application still loading selected assets from a CDN.",
"default-src 'self'; script-src 'self' https://cdn.service.local; style-src 'self' https://cdn.service.local; img-src 'self' https://cdn.service.local data:; font-src 'self' https://cdn.service.local; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests",
"Prefer explicit CDN hosts over scheme-wide https: allowlists.",
)
}
///|
pub fn strict_api_docs_template() -> PolicyTemplate {
make_policy_template(
"strict-api-docs", "Strict API docs", "Documentation with API examples but no live remote execution.",
"default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'none'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'none'; upgrade-insecure-requests",
"connect-src none prevents accidental live calls from static docs.",
)
}
///|
pub fn service_worker_template() -> PolicyTemplate {
make_policy_template(
"service-worker-app", "Service worker app", "Offline-capable app with a service worker.",
"default-src 'self'; script-src 'self' 'nonce-demo'; worker-src 'self'; connect-src 'self' https:; img-src 'self' data:; style-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests",
"worker-src should remain explicit for service worker heavy apps.",
)
}
///|
pub fn media_library_template() -> PolicyTemplate {
make_policy_template(
"media-library", "Media library", "App that streams images, audio, and video from controlled origins.",
"default-src 'self'; script-src 'self' 'nonce-demo'; style-src 'self'; img-src 'self' https://media.service.local data:; media-src 'self' https://media.service.local; connect-src 'self' https://api.service.local; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests",
"Keep media and API origins separate for easier review.",
)
}
///|
pub fn upload_portal_template() -> PolicyTemplate {
make_policy_template(
"upload-portal", "Upload portal", "Authenticated upload page with previews.",
"default-src 'self'; script-src 'self' 'nonce-demo'; style-src 'self'; img-src 'self' blob: data:; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests",
"Blob previews are common for upload flows but should stay out of script-src.",
)
}
///|
pub fn payment_checkout_template() -> PolicyTemplate {
make_policy_template(
"payment-checkout", "Payment checkout", "Checkout page with one approved payment frame.",
"default-src 'self'; script-src 'self' 'nonce-demo' https://pay.service.local; style-src 'self'; img-src 'self' https: data:; frame-src https://pay.service.local; connect-src 'self' https://pay.service.local; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self' https://pay.service.local; upgrade-insecure-requests",
"Payment integrations should be isolated and explicitly allowed.",
)
}
///|
pub fn analytics_limited_template() -> PolicyTemplate {
make_policy_template(
"analytics-limited", "Limited analytics", "Site with one analytics endpoint and no third-party script execution.",
"default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: https://analytics.service.local; connect-src 'self' https://analytics.service.local; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests",
"Prefer server-side analytics or explicit endpoints over broad script-src hosts.",
)
}
///|
pub fn local_dev_template() -> PolicyTemplate {
make_policy_template(
"local-dev", "Local development", "Development-only policy with local endpoints.",
"default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' http://localhost:* ws://localhost:*; object-src 'none'; base-uri 'self'; frame-ancestors 'none'",
"This template intentionally contains development risks and should not be used in production.",
)
}
///|
pub fn production_spa_template() -> PolicyTemplate {
make_policy_template(
"production-spa", "Production SPA", "Production single page app with strict script trust.",
"default-src 'self'; script-src 'self' 'nonce-demo' 'strict-dynamic'; style-src 'self'; img-src 'self' https: data:; connect-src 'self' https://api.service.local; font-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'; require-trusted-types-for 'script'; trusted-types default; upgrade-insecure-requests",
"A strong baseline for modern apps that can deploy nonces.",
)
}
///|
pub fn locked_iframe_template() -> PolicyTemplate {
make_policy_template(
"locked-iframe", "Locked iframe", "Isolated iframe used for previews or external content wrappers.",
"default-src 'none'; script-src 'none'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'none'; sandbox",
"Useful when the content should not run active script.",
)
}
///|
pub fn no_script_report_template() -> PolicyTemplate {
make_policy_template(
"no-script-report", "No script report", "Static generated report with images and styles only.",
"default-src 'none'; script-src 'none'; style-src 'self'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'none'",
"A good high-security option for exported reports.",
)
}
///|
pub fn trusted_types_app_template() -> PolicyTemplate {
make_policy_template(
"trusted-types-app", "Trusted Types app", "Script-heavy web app with DOM injection sink protection.",
"default-src 'self'; script-src 'self' 'nonce-demo' 'strict-dynamic'; style-src 'self'; img-src 'self' data: https:; connect-src 'self' https://api.service.local; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; require-trusted-types-for 'script'; trusted-types default app-html sanitizer; upgrade-insecure-requests",
"Trusted Types policy names should match audited application code.",
)
}
///|
pub fn worker_compute_template() -> PolicyTemplate {
make_policy_template(
"worker-compute", "Worker compute", "Browser compute task that isolates work into workers.",
"default-src 'self'; script-src 'self' 'nonce-demo'; worker-src 'self' blob:; connect-src 'self'; img-src 'self' data:; style-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'none'; upgrade-insecure-requests",
"blob: in worker-src may be acceptable when workers are generated locally.",
)
}
///|
pub fn offline_docs_template() -> PolicyTemplate {
make_policy_template(
"offline-docs", "Offline docs", "Documentation bundle that should work without network access.",
"default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'none'; media-src 'none'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'none'",
"The explicit connect-src none makes offline behavior visible.",
)
}
///|
pub fn all_policy_templates() -> Array[PolicyTemplate] {
let templates : Array[PolicyTemplate] = []
templates.push(strict_static_site_template())
templates.push(nonce_spa_template())
templates.push(wasm_app_template())
templates.push(api_console_template())
templates.push(admin_panel_template())
templates.push(markdown_preview_template())
templates.push(embedded_widget_template())
templates.push(locked_down_document_template())
templates.push(report_only_migration_template())
templates.push(legacy_cdn_template())
templates.push(strict_api_docs_template())
templates.push(service_worker_template())
templates.push(media_library_template())
templates.push(upload_portal_template())
templates.push(payment_checkout_template())
templates.push(analytics_limited_template())
templates.push(local_dev_template())
templates.push(production_spa_template())
templates.push(locked_iframe_template())
templates.push(no_script_report_template())
templates.push(trusted_types_app_template())
templates.push(worker_compute_template())
templates.push(offline_docs_template())
templates
}
///|
pub fn policy_template_names() -> Array[String] {
let names : Array[String] = []
for template in all_policy_templates() {
names.push(template.name)
}
names
}
///|
pub fn policy_template_by_name(name : StringView) -> PolicyTemplate? {
let key = lower_ascii(name)
for template in all_policy_templates() {
if template.name == key {
return Some(template)
}
}
None
}
///|
pub fn valid_policy_templates() -> Array[PolicyTemplate] {
let out : Array[PolicyTemplate] = []
for template in all_policy_templates() {
match template.parse() {
Ok(_) => out.push(template)
Err(_) => ()
}
}
out
}
///|
pub fn template_scoreboard() -> String {
let rows : Array[String] = []
for template in all_policy_templates() {
match template.score() {
Some(score) => rows.push(template.name + " " + score.summary())
None => rows.push(template.name + " invalid")
}
}
rows.join("\n")
}
///|
pub fn compare_policy_templates(
before_name : StringView,
after_name : StringView,
) -> PolicyDiff? {
match
(policy_template_by_name(before_name), policy_template_by_name(after_name)) {
(Some(before_template), Some(after_template)) =>
match (before_template.parse(), after_template.parse()) {
(Ok(before), Ok(after)) => Some(diff_policy(before, after))
(_, _) => None
}
(_, _) => None
}
}
///|
pub fn template_audit_reports() -> String {
let reports : Array[String] = []
for template in all_policy_templates() {
reports.push(template.audit_text_report())
}
reports.join("\n\n")
}
///|
pub fn safest_policy_template() -> PolicyTemplate? {
let templates = all_policy_templates()
if templates.length() == 0 {
return None
}
let mut best = templates[0]
let mut best_score = -1
for template in templates {
match template.score() {
Some(score) =>
if score.final_score > best_score {
best = template
best_score = score.final_score
}
None => ()
}
}
Some(best)
}
///|
pub fn riskiest_policy_template() -> PolicyTemplate? {
let templates = all_policy_templates()
if templates.length() == 0 {
return None
}
let mut worst = templates[0]
let mut worst_score = 101
for template in templates {
match template.score() {
Some(score) =>
if score.final_score < worst_score {
worst = template
worst_score = score.final_score
}
None => ()
}
}
Some(worst)
}
///|
pub fn templates_with_high_findings() -> Array[PolicyTemplate] {
let out : Array[PolicyTemplate] = []
for template in all_policy_templates() {
match template.score() {
Some(score) => if score.findings.high > 0 { out.push(template) }
None => ()
}
}
out
}
///|
pub fn templates_without_high_findings() -> Array[PolicyTemplate] {
let out : Array[PolicyTemplate] = []
for template in all_policy_templates() {
match template.score() {
Some(score) => if score.findings.high == 0 { out.push(template) }
None => ()
}
}
out
}
///|
pub fn template_catalog_summary() -> String {
"templates=\{all_policy_templates().length()} valid=\{valid_policy_templates().length()} clean=\{templates_without_high_findings().length()} risky=\{templates_with_high_findings().length()}"
}