From 710aba627814b2f304a13f4f9bd72239a6c8affd Mon Sep 17 00:00:00 2001 From: tristanroth1 Date: Mon, 26 Oct 2026 17:00:59 +0100 Subject: [PATCH 1/3] site: add DSA report-content page Notice-and-action page (DSA Art. 16) and user point of contact (Art. 12) at /report/, with a prefilled email to the existing Better ISMS privacy mailbox and a private GitHub advisory as the alternative. Footer link, sitemap entry, reserved author slug, README pointer, changelog. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 + README.md | 2 + site/app/layout.tsx | 1 + site/app/report/page.tsx | 74 ++++++++++++++++++++++++++++++++++ site/scripts/build-content.mjs | 2 +- site/scripts/content-lib.mjs | 2 +- 6 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 site/app/report/page.tsx 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..70b5f09 --- /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:', + 'Your email:', + '', + 'I confirm, in good faith, that the information and allegations in this notice are accurate and complete.', + '', +].join('\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.', + '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, act on it (for example by correcting or removing the content and cutting a new release), and reply to you with our decision. 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', ]); From 08f3c97acf6378ed40009bdf37403b31cfdeb9f9 Mon Sep 17 00:00:00 2001 From: tristanroth1 Date: Mon, 26 Oct 2026 17:04:43 +0100 Subject: [PATCH 2/3] site: fold review round 1 (Art. 16(5)-(6) handling wording, CSAM name exception, CRLF mailto, built test) Co-Authored-By: Claude Opus 5.5 --- site/app/report/page.tsx | 10 +++++----- site/tests/built.test.mjs | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/site/app/report/page.tsx b/site/app/report/page.tsx index 70b5f09..acbfbbc 100644 --- a/site/app/report/page.tsx +++ b/site/app/report/page.tsx @@ -16,18 +16,18 @@ const BODY = [ '', 'Why the content is illegal or infringes your rights (for copyright: the work and the rights you hold):', '', - 'Your name:', - 'Your email:', + '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('\n'); +].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.', + '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.', ]; @@ -58,7 +58,7 @@ export default function Report() {

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, act on it (for example by correcting or removing the content and cutting a new release), and reply to you with our decision. We may also tell the contributor what we removed and why.

+

We confirm that we received your notice, then review it promptly, carefully and objectively, and decide whether to act on it (for example by correcting or removing the content and cutting a new release). We 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.

diff --git a/site/tests/built.test.mjs b/site/tests/built.test.mjs index 5b69ca4..3248df8 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, /—/, 'no em dash on report'); +}); From a0ec1debd3ed2bd4f0650dbbd450877693c6e3da Mon Sep 17 00:00:00 2001 From: tristanroth1 Date: Mon, 26 Oct 2026 17:07:22 +0100 Subject: [PATCH 3/3] site: fold review round 2 (conditional receipt and reply, escape em dash in test) Co-Authored-By: Claude Opus 5.5 --- site/app/report/page.tsx | 2 +- site/tests/built.test.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/site/app/report/page.tsx b/site/app/report/page.tsx index acbfbbc..f40485e 100644 --- a/site/app/report/page.tsx +++ b/site/app/report/page.tsx @@ -58,7 +58,7 @@ export default function Report() {

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 confirm that we received your notice, then review it promptly, carefully and objectively, and decide whether to act on it (for example by correcting or removing the content and cutting a new release). We 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.

+

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.

diff --git a/site/tests/built.test.mjs b/site/tests/built.test.mjs index 3248df8..a002ec7 100644 --- a/site/tests/built.test.mjs +++ b/site/tests/built.test.mjs @@ -373,5 +373,5 @@ test('report: DSA notice page is built, linked, in the sitemap, with a CRLF pref 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, /—/, 'no em dash on report'); + assert.doesNotMatch(html, /\u2014/, 'no em dash on report'); });