feat(marketing): add security bug bounty policy (#29)

This commit is contained in:
hampus-fluxer
2026-01-05 14:28:04 +01:00
committed by GitHub
parent 81402413f1
commit a9da71c7d7
48 changed files with 902 additions and 343 deletions

View File

@@ -260,6 +260,17 @@ pub fn render(ctx: Context) -> Element(a) {
[html.text(g_(i18n_ctx, "Community Guidelines"))],
),
]),
html.li([], [
html.a(
[
href(ctx, "/security"),
attribute.class(
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
),
],
[html.text(g_(i18n_ctx, "Security Bug Bounty"))],
),
]),
html.li([], [
html.a(
[

View File

@@ -189,10 +189,10 @@ pub fn render(ctx: Context) -> Element(a) {
g_(i18n_ctx, "Found a security issue?"),
g_(
i18n_ctx,
"We appreciate responsible disclosure to security@fluxer.app. We offer Plutonium codes and Bug Hunter badges based on severity.",
"We appreciate responsible disclosure via our Security Bug Bounty page. We offer Plutonium codes and Bug Hunter badges based on severity.",
),
"security@fluxer.app",
"mailto:security@fluxer.app",
g_(i18n_ctx, "Security Bug Bounty"),
"/security",
),
],
),

View File

@@ -414,6 +414,15 @@ pub fn render(ctx: Context, _req: Request) -> Element(a) {
],
[html.text(g_(i18n_ctx, "Community Guidelines"))],
),
html.a(
[
href(ctx, "/security"),
attribute.class(
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
),
],
[html.text(g_(i18n_ctx, "Security Bug Bounty"))],
),
]),
]),
html.div([], [

View File

@@ -153,8 +153,8 @@ fn contribute_section(ctx: Context) -> Element(a) {
i18n_ctx,
"Report vulnerabilities and help us keep the platform secure.",
),
"security@fluxer.app",
"mailto:security@fluxer.app",
g_(i18n_ctx, "Security Bug Bounty"),
"/security",
),
support_card.render(
ctx,

View File

@@ -0,0 +1,49 @@
//// Copyright (C) 2026 Fluxer Contributors
////
//// This file is part of Fluxer.
////
//// Fluxer is free software: you can redistribute it and/or modify
//// it under the terms of the GNU Affero General Public License as published by
//// the Free Software Foundation, either version 3 of the License, or
//// (at your option) any later version.
////
//// Fluxer is distributed in the hope that it will be useful,
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//// GNU Affero General Public License for more details.
////
//// You should have received a copy of the GNU Affero General Public License
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
import fluxer_marketing/help_center
import fluxer_marketing/i18n
import fluxer_marketing/markdown_utils
import fluxer_marketing/pages/layout
import fluxer_marketing/web.{type Context}
import kielet.{gettext as g_}
import lustre/element
import wisp
pub fn render(req: wisp.Request, ctx: Context) -> wisp.Response {
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
let help_data = help_center.load_help_articles(ctx.locale)
let markdown_element =
markdown_utils.load_markdown_with_fallback("priv/security", ctx.locale)
|> markdown_utils.render_markdown_to_element(ctx, help_data)
let content = [markdown_element]
layout.docs_layout(
req,
ctx,
layout.article_page_meta(
g_(i18n_ctx, "Security Bug Bounty"),
g_(i18n_ctx, "Report security issues responsibly"),
),
g_(i18n_ctx, "Fluxer Security Bug Bounty"),
content,
)
|> element.to_document_string_tree
|> wisp.html_response(200)
}

View File

@@ -291,7 +291,7 @@ fn is_table(text: String) -> Bool {
[_header, separator, ..] -> {
case
regexp.compile(
"^\\|?\\s*:?-+:?\\s*(?:\\|\\s*:?-+:?\\s*)+\\|?$",
"^\\|?\\s*:?-+:?\\s*(?:\\|\\s*:?-+:?\\s*)*\\|?$",
regex_options,
)
{

View File

@@ -34,10 +34,12 @@ import fluxer_marketing/pages/partners_page
import fluxer_marketing/pages/plutonium_page
import fluxer_marketing/pages/press_page
import fluxer_marketing/pages/privacy_page
import fluxer_marketing/pages/security_page
import fluxer_marketing/pages/terms_page
import fluxer_marketing/sitemap
import fluxer_marketing/web.{type Context, prepend_base_path}
import gleam/http
import gleam/int
import gleam/list
import gleam/option
import gleam/string
@@ -60,6 +62,8 @@ pub fn handle_request(req: Request, ctx: Context) -> Response {
["sitemap.xml"] -> handle_sitemap(ctx)
["terms"] -> terms_page.render(req, ctx)
["privacy"] -> privacy_page.render(req, ctx)
["security"] -> security_page.render(req, ctx)
["security.txt"] -> handle_security_txt(ctx)
["guidelines"] -> guidelines_page.render(req, ctx)
["company-information"] -> company_page.render(req, ctx)
["careers"] -> careers_page.render(req, ctx)
@@ -256,6 +260,36 @@ fn handle_sitemap(ctx: Context) -> Response {
|> wisp.string_body(xml)
}
fn handle_security_txt(ctx: Context) -> Response {
let security_url = ctx.base_url <> "/security"
let expires = int.to_string(current_year() + 1) <> "-01-05T13:37:00.000Z"
let body_lines =
[
"Contact: " <> security_url,
"Contact: mailto:security@fluxer.app",
"Expires: " <> expires,
"Preferred-Languages: en",
"Policy: " <> security_url,
]
|> string.join("\n")
let body = body_lines <> "\n"
wisp.response(200)
|> wisp.set_header("content-type", "text/plain; charset=utf-8")
|> wisp.string_body(body)
}
fn current_year() -> Int {
case calendar_universal_time() {
#(#(year, _, _), _) -> year
}
}
@external(erlang, "calendar", "universal_time")
fn calendar_universal_time() -> #(#(Int, Int, Int), #(Int, Int, Int))
fn handle_locale_change(req: Request, ctx: Context) -> Response {
case req.method {
http.Post -> {

View File

@@ -47,6 +47,7 @@ fn generate_urls(base_url: String) -> List(UrlEntry) {
UrlEntry(base_url, "weekly", "1.0"),
UrlEntry(base_url <> "/terms", "monthly", "0.5"),
UrlEntry(base_url <> "/privacy", "monthly", "0.5"),
UrlEntry(base_url <> "/security", "monthly", "0.5"),
UrlEntry(base_url <> "/guidelines", "monthly", "0.7"),
UrlEntry(base_url <> "/company-information", "monthly", "0.4"),
UrlEntry(base_url <> "/careers", "weekly", "0.6"),