diff --git a/CHANGELOG.md b/CHANGELOG.md index ee33c26..db83ae0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Release tags are `vYYYY.MM.DD[.n]`. Installers and the Claude Code plugin consum ## Unreleased +- grcskills.com/report/: how to report illegal content or a copyright issue (EU Digital Services Act Art. 16 notices, Art. 12 point of contact), with a prefilled email and a private GitHub advisory as the two channels. Linked from the footer, listed in the sitemap, mentioned in the README; `report` is a reserved author slug. + ## v2026.10.20.3 - ISMS Copilot, the GRC specialist your AI calls, gets its own place: a home band after "What comes back" with the connect URL and a copy button, a "Connect your AI" menu link, a clearer note under "Run with ISMS Copilot" on every skill page, and an FAQ answer on how to connect. Each link carries its own `ref=grcskills-*`. The heyGRC bar, home band and cards are unchanged (#14). diff --git a/README.md b/README.md index 986f7aa..1749d81 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,8 @@ assets/ README banner, social preview, demo run Authors can ask for a correction to their profile or skill, or for removal, by opening an issue (or a private security advisory if it is sensitive). Maintainers act on it promptly; a removed author's profile and skills leave the next release. +To report illegal content or a copyright issue (EU Digital Services Act notices included), see [grcskills.com/report](https://grcskills.com/report/): it lists what to include and how to send it privately. + ## Credits and notices By [ISMS Copilot](https://www.ismscopilot.com/mcp?ref=grcskills-readme), maintained by Better ISMS. MIT licensed ([LICENSE](LICENSE)); each skill credits its author. diff --git a/site/app/layout.tsx b/site/app/layout.tsx index 1d6591c..5569921 100644 --- a/site/app/layout.tsx +++ b/site/app/layout.tsx @@ -87,6 +87,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) Skills Contribute FAQ + Report content GitHub llms.txt diff --git a/site/app/report/page.tsx b/site/app/report/page.tsx new file mode 100644 index 0000000..f40485e --- /dev/null +++ b/site/app/report/page.tsx @@ -0,0 +1,74 @@ +import type { Metadata } from 'next'; +import Link from 'next/link'; +import Breadcrumbs from '@/components/Breadcrumbs'; +import { meta } from '@/lib/content'; +import { pageMeta } from '@/lib/seo'; + +// Notice-and-action mechanism (EU Digital Services Act Art. 16), point of +// contact for users (Art. 12) and copyright notices. Reports go to the privacy +// mailbox Better ISMS already publishes on its sites, or to a private GitHub +// security advisory for people who prefer GitHub. + +const REPORT_EMAIL = 'privacy@ismscopilot.com'; +const SUBJECT = 'Content report: grcskills'; +const BODY = [ + 'Exact URL of the content:', + '', + 'Why the content is illegal or infringes your rights (for copyright: the work and the rights you hold):', + '', + 'Your name (optional for notices about child sexual abuse offences):', + 'Your email (optional for notices about child sexual abuse offences):', + '', + 'I confirm, in good faith, that the information and allegations in this notice are accurate and complete.', + '', +].join('\r\n'); +const MAILTO = `mailto:${REPORT_EMAIL}?subject=${encodeURIComponent(SUBJECT)}&body=${encodeURIComponent(BODY)}`; + +const FIELDS = [ + 'The exact URL of the content (a skill page, an author page, or a file on GitHub).', + 'A clear explanation of why you believe the content is illegal or infringes your rights. For copyright, identify the work and the rights you hold.', + 'Your name and email address. You may leave these out if your notice concerns child sexual abuse material or another offence covered by Articles 3 to 7 of Directive 2011/93/EU.', + 'A statement that you believe, in good faith, that the information and allegations in your notice are accurate and complete.', +]; + +export const metadata: Metadata = pageMeta({ + title: 'Report content', + description: 'How to report illegal content or a copyright issue on grcskills, and how to contact us.', + path: '/report/', +}); + +export default function Report() { + return ( +
+ +
+

Report content

+

Report illegal content or a copyright issue in a skill or an author profile.

+
+ +
+
+

What you can report

+

Skills and author profiles are written by contributors. If you believe something here is illegal, or uses your copyright or other rights without permission, tell us. This page is our notice mechanism under the EU Digital Services Act (Article 16) and the way to send us a copyright notice.

+ +

What to include

+
    + {FIELDS.map((f) =>
  • {f}
  • )} +
+ +

How to send it

+

Email {REPORT_EMAIL}. The link opens a message with the fields above already laid out. If you prefer GitHub, you can send the same information privately through a private GitHub advisory; please do not put personal details in a public issue.

+

We review each notice promptly, carefully and objectively, and decide whether to act on it (for example by correcting or removing the content and cutting a new release). If you give us your email address, we confirm that we received your notice and tell you our decision without undue delay, with the reasons and how you can contest it. We may also tell the contributor what we removed and why.

+
+
+ +
+
+

Point of contact

+

The same address is our single point of contact for users of grcskills (Digital Services Act, Article 12). You can write to us in English or French. For a question about a skill, see the FAQ.

+
+ Report content by email +
+
+ ); +} diff --git a/site/scripts/build-content.mjs b/site/scripts/build-content.mjs index 0b6934f..a879170 100644 --- a/site/scripts/build-content.mjs +++ b/site/scripts/build-content.mjs @@ -246,7 +246,7 @@ export async function main() { write('public/build.json', `${JSON.stringify({ sha, tag, builtAt: meta.builtAt, skills: skills.length }, null, 2)}\n`); const urls = [ - ['/', allDates], ['/skills/', allDates], ['/contribute/', allDates], ['/faq/', allDates], + ['/', allDates], ['/skills/', allDates], ['/contribute/', allDates], ['/faq/', allDates], ['/report/', allDates], ...skills.map((s) => [`/skill/${s.slug}/`, s.dateModified]), ...skills.flatMap((s) => s.references.map((r) => [`/skill/${s.slug}/${r.slug}/`, s.dateModified])), ...authors.filter((a) => a.skills.length).map((a) => [`/${a.slug}/`, a.dateModified]), diff --git a/site/scripts/content-lib.mjs b/site/scripts/content-lib.mjs index 6d38d06..4e97d58 100644 --- a/site/scripts/content-lib.mjs +++ b/site/scripts/content-lib.mjs @@ -59,7 +59,7 @@ export const FRAMEWORK_LABELS = { // Author slugs become top-level routes (//), so they must not // shadow a site route or a generated file. export const RESERVED_SLUGS = new Set([ - 'skill', 'skills', 'category', 'categories', 'framework', 'frameworks', 'contribute', 'faq', 'about', + 'skill', 'skills', 'category', 'categories', 'framework', 'frameworks', 'contribute', 'faq', 'report', 'about', 'search', 'api', 'llms', 'sitemap', 'robots', 'build', 'og', 'favicon', 'inline', 'static', 'assets', 'fonts', 'legal', 'privacy', 'terms', 'security', 'admin', 'login', 'index', '404', ]); diff --git a/site/tests/built.test.mjs b/site/tests/built.test.mjs index 5b69ca4..a002ec7 100644 --- a/site/tests/built.test.mjs +++ b/site/tests/built.test.mjs @@ -364,3 +364,14 @@ test('top bar: copy at most 90 characters with the link, no em dash; topbar.js s assert.doesNotMatch(js, /fetch|XMLHttpRequest|sendBeacon|document\.cookie/, 'no network, no cookies'); assert.ok(src.includes(`long: '${TOPBAR_LONG}'`) && src.includes(`short: '${TOPBAR_SHORT}'`), 'test copy matches lib/content.ts'); }); + +test('report: DSA notice page is built, linked, in the sitemap, with a CRLF prefilled mailto', opts, () => { + const html = read('report/index.html'); + for (const field of ['exact URL', 'Your name and email address', 'good faith', 'Article 16', 'Article 12', 'Directive 2011/93/EU', 'how you can contest it']) { + assert.ok(html.includes(field), `report page mentions ${field}`); + } + assert.match(html, /href="mailto:privacy@ismscopilot\.com\?subject=[^"]+&body=[^"]*%0D%0A/, 'prefilled mailto with CRLF line breaks'); + assert.ok(read('index.html').includes('href="/report/"'), 'footer links the report page'); + assert.ok(read('sitemap.xml').includes('https://grcskills.com/report/'), 'report in sitemap'); + assert.doesNotMatch(html, /\u2014/, 'no em dash on report'); +});