Skip to content

Spam protection and API keys for creating pastes - #30

Merged
Musiker15 merged 3 commits into
mainfrom
feat/spam-protection
Oct 30, 2026
Merged

Musiker15 merged 3 commits into
mainfrom
feat/spam-protection

Conversation

@Musiker15

Copy link
Copy Markdown
Member

Why

The database is mostly spam. 95 % of all pastes come from one address that uses the service as file storage: blocks of about 0.5 MB of base64, kept for a year, 40 to 90 a day since September 22. The rest is SEO and comment spam built around links.

The only brake was an in-memory limit per address, and that address came from CF-Connecting-IP or the first X-Forwarded-For entry. Both are set by the client, so a script could pick a new address per request. The site is not behind Cloudflare.

What

Client address. X-Real-IP (Apache sets it from the connection) or the last X-Forwarded-For entry, which Apache appends. Never a header the client chooses.

The form, in order of cost:

  • same origin
  • an untouched honeypot field
  • a signed form stamp, at least 2 s and at most 24 h old
  • content checks (below)
  • 20 pastes and 5 MB per address and day, counted in the database
  • Cloudflare Turnstile, last, because verifying spends the token. Required in production and fails closed: without keys nothing can be created there.

Scripts need an API key (Authorization: Bearer mskp_…). Without a key the API answers 401. Keys are managed on the server with scripts/api-key.sh (help, list, create, revoke, delete), only the SHA-256 is stored (migration 003_api_keys.sql). A key skips the form checks and has its own limit of 120 per hour.

Content checks for both ways: an unbroken run of 16 KB of base64/hex, or a large paste made almost only of wrapped base64, is an encoded file. Text that is mostly links, or forum markup with several links, is link spam. The same content again within 24 hours is a repeat.

Privacy policy (Turnstile, retention of the IP hash), README (API key, key commands) and .env.example are updated. Vitest covers the rules, and a new build workflow type-checks, tests and builds every pull request.

Before merging

The server's /opt/msk-paste/.env needs CAPTCHA_SITE_KEY and CAPTCHA_SECRET_KEY from a Turnstile widget for paste.msk-scripts.de. Without them the form refuses to create pastes in production.

Checked

  • 42 tests. Counter-checked by breaking three rules (first XFF entry, no action check, honeypot off), each turned the tests red.
  • Locally against MariaDB 11 with Cloudflare's test keys:
    • form creates
    • duplicate, blob, link spam and honeypot are refused with their message
    • API without key and with a wrong key 401, with a key 201
    • after revoke 401
    • list, create, revoke, delete, help work
  • Production build: Turnstile loads under the nonce CSP without violations, and a test-key result is refused there.

95 % of all pastes came from one address that used the service as file
storage (blocks of about 0.5 MB of base64, one year each), the rest was
link and comment spam. The create route had only an in-memory limit per
address, and that address came from CF-Connecting-IP or the first
X-Forwarded-For entry, both set by the client.

- The client address is X-Real-IP (set by Apache) or the last
  X-Forwarded-For entry, never a header the client chooses.
- The form needs a solved Cloudflare Turnstile (required in production,
  fails closed), the same origin, an untouched honeypot and a signed form
  stamp at least two seconds old. Per address it may create 20 pastes and
  5 MB a day, counted in the database.
- Scripts need an API key. scripts/api-key.sh (help, list, create, revoke,
  delete) manages them on the server; only the SHA-256 is stored.
- Both ways pass content checks: encoded files, link spam and the same
  content again within a day are turned down.
- The privacy policy names Turnstile, the README the key requirement.
- Vitest for the rules, and a build workflow that type-checks, tests and
  builds every pull request.
@github-actions

Copy link
Copy Markdown

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 2 package(s) with unknown licenses.
  • ⚠️ 1 packages with OpenSSF Scorecard issues.
See the Details below.

License Issues

.github/workflows/build.yml

PackageVersionLicenseIssue Type
actions/checkout6.*.*NullUnknown License
actions/setup-node6.*.*NullUnknown License
Denied Licenses: GPL-2.0, GPL-3.0

OpenSSF Scorecard

Scorecard details
PackageVersionScoreDetails
actions/actions/checkout 6.*.* 🟢 5.9
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Maintained⚠️ 00 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies🟢 3dependency not pinned by hash detected -- score normalized to 3
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Packaging⚠️ -1packaging workflow not detected
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
SAST🟢 10SAST tool is run on all commits
Branch-Protection🟢 6branch protection is not maximal on development and all release branches
actions/actions/setup-node 6.*.* 🟢 6.4
Details
CheckScoreReason
Maintained🟢 1016 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Binary-Artifacts🟢 9binaries present in source code
Code-Review🟢 10all changesets reviewed
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies🟢 5dependency not pinned by hash detected -- score normalized to 5
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST🟢 10SAST tool is run on all commits
npm/@jridgewell/sourcemap-codec 1.6.0 UnknownUnknown
npm/@oxc-project/types 0.156.0 UnknownUnknown
npm/@rolldown/binding-android-arm-eabi 1.2.16 UnknownUnknown
npm/@rolldown/binding-android-arm64 1.2.16 UnknownUnknown
npm/@rolldown/binding-darwin-arm64 1.2.16 UnknownUnknown
npm/@rolldown/binding-darwin-x64 1.2.16 UnknownUnknown
npm/@rolldown/binding-freebsd-x64 1.2.16 UnknownUnknown
npm/@rolldown/binding-linux-arm-gnueabihf 1.2.16 UnknownUnknown
npm/@rolldown/binding-linux-arm64-gnu 1.2.16 UnknownUnknown
npm/@rolldown/binding-linux-arm64-musl 1.2.16 UnknownUnknown
npm/@rolldown/binding-linux-ppc64-gnu 1.2.16 UnknownUnknown
npm/@rolldown/binding-linux-s390x-gnu 1.2.16 UnknownUnknown
npm/@rolldown/binding-linux-x64-gnu 1.2.16 UnknownUnknown
npm/@rolldown/binding-linux-x64-musl 1.2.16 UnknownUnknown
npm/@rolldown/binding-openharmony-arm64 1.2.16 UnknownUnknown
npm/@rolldown/binding-win32-arm64-msvc 1.2.16 UnknownUnknown
npm/@rolldown/binding-win32-x64-msvc 1.2.16 UnknownUnknown
npm/@rolldown/pluginutils 1.0.1 UnknownUnknown
npm/@types/chai 5.2.3 🟢 6.6
Details
CheckScoreReason
Code-Review🟢 9Found 28/30 approved changesets -- score normalized to 9
Packaging⚠️ -1packaging workflow not detected
Maintained🟢 1030 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Security-Policy🟢 10security policy file detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
License🟢 9license file detected
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
Signed-Releases⚠️ -1no releases found
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
Fuzzing⚠️ 0project is not fuzzed
npm/@types/deep-eql 4.0.2 🟢 6.6
Details
CheckScoreReason
Code-Review🟢 9Found 28/30 approved changesets -- score normalized to 9
Packaging⚠️ -1packaging workflow not detected
Maintained🟢 1030 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Security-Policy🟢 10security policy file detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
License🟢 9license file detected
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
Signed-Releases⚠️ -1no releases found
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
Fuzzing⚠️ 0project is not fuzzed
npm/@vitest/mocker 5.0.5 UnknownUnknown
npm/@vitest/spy 5.0.5 UnknownUnknown
npm/assertion-error 2.0.1 🟢 4.7
Details
CheckScoreReason
Maintained⚠️ 00 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Binary-Artifacts🟢 10no binaries found in the repo
Code-Review🟢 7Found 20/26 approved changesets -- score normalized to 7
Packaging⚠️ -1packaging workflow not detected
Pinned-Dependencies🟢 10all dependencies are pinned
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Security-Policy⚠️ 0security policy file not detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/chai 6.3.0 🟢 6.9
Details
CheckScoreReason
Code-Review🟢 6Found 6/9 approved changesets -- score normalized to 6
Maintained🟢 1030 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Security-Policy⚠️ 0security policy file not detected
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Packaging🟢 10packaging workflow detected
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/es-module-lexer 2.3.2 🟢 4.8
Details
CheckScoreReason
Code-Review⚠️ 1Found 5/30 approved changesets -- score normalized to 1
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Maintained🟢 1030 commit(s) and 7 issue activity found in the last 90 days -- score normalized to 10
Binary-Artifacts🟢 10no binaries found in the repo
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Packaging🟢 10packaging workflow detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
Security-Policy⚠️ 0security policy file not detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/estree-walker 3.0.3 ⚠️ 2.1
Details
CheckScoreReason
Packaging⚠️ -1packaging workflow not detected
Binary-Artifacts🟢 10no binaries found in the repo
Code-Review⚠️ 1Found 4/21 approved changesets -- score normalized to 1
Dangerous-Workflow⚠️ -1no workflows found
Maintained⚠️ 00 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Token-Permissions⚠️ -1No tokens found
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies⚠️ -1no dependencies found
Security-Policy⚠️ 0security policy file not detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/expect-type 1.4.0 🟢 4.4
Details
CheckScoreReason
Packaging⚠️ -1packaging workflow not detected
Binary-Artifacts🟢 10no binaries found in the repo
Code-Review🟢 3Found 10/27 approved changesets -- score normalized to 3
Security-Policy🟢 10security policy file detected
Pinned-Dependencies⚠️ 1dependency not pinned by hash detected -- score normalized to 1
Maintained⚠️ 00 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 9license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection🟢 6branch protection is not maximal on development and all release branches
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/lightningcss 1.33.0 UnknownUnknown
npm/lightningcss-android-arm64 1.33.0 UnknownUnknown
npm/lightningcss-darwin-arm64 1.33.0 UnknownUnknown
npm/lightningcss-darwin-x64 1.33.0 UnknownUnknown
npm/lightningcss-freebsd-x64 1.33.0 UnknownUnknown
npm/lightningcss-linux-arm-gnueabihf 1.33.0 UnknownUnknown
npm/lightningcss-linux-arm64-gnu 1.33.0 UnknownUnknown
npm/lightningcss-linux-arm64-musl 1.33.0 UnknownUnknown
npm/lightningcss-linux-x64-gnu 1.33.0 UnknownUnknown
npm/lightningcss-linux-x64-musl 1.33.0 UnknownUnknown
npm/lightningcss-win32-arm64-msvc 1.33.0 UnknownUnknown
npm/lightningcss-win32-x64-msvc 1.33.0 UnknownUnknown
npm/magic-string 1.4.5 🟢 5.3
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 6 issue activity found in the last 90 days -- score normalized to 10
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Code-Review🟢 6Found 19/30 approved changesets -- score normalized to 6
Token-Permissions🟢 9detected GitHub workflow tokens with excessive permissions
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Security-Policy⚠️ 0security policy file not detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Signed-Releases⚠️ -1no releases found
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/obug 3.0.0 UnknownUnknown
npm/picomatch 4.0.7 🟢 6.8
Details
CheckScoreReason
Code-Review🟢 5Found 7/13 approved changesets -- score normalized to 5
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Maintained🟢 1027 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies🟢 6dependency not pinned by hash detected -- score normalized to 6
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Security-Policy🟢 10security policy file detected
SAST🟢 5SAST tool is not run on all commits -- score normalized to 5
npm/rolldown 1.2.16 UnknownUnknown
npm/std-env 4.3.0 UnknownUnknown
npm/tinybench 6.2.2 UnknownUnknown
npm/tinyexec 1.3.1 UnknownUnknown
npm/vite 8.3.5 🟢 6.5
Details
CheckScoreReason
Code-Review🟢 5Found 13/24 approved changesets -- score normalized to 5
Maintained🟢 1030 commit(s) and 8 issue activity found in the last 90 days -- score normalized to 10
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Security-Policy🟢 10security policy file detected
Token-Permissions🟢 7detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts⚠️ 1binaries present in source code
Pinned-Dependencies🟢 9dependency not pinned by hash detected -- score normalized to 9
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Branch-Protection🟢 4branch protection is not maximal on development and all release branches
SAST🟢 10SAST tool is run on all commits
npm/vitest 5.0.5 UnknownUnknown
npm/why-is-node-running 3.2.1 🟢 4.7
Details
CheckScoreReason
Binary-Artifacts🟢 10no binaries found in the repo
Maintained🟢 53 commit(s) and 4 issue activity found in the last 90 days -- score normalized to 5
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
Token-Permissions🟢 9detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Packaging🟢 10packaging workflow detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Security-Policy⚠️ 0security policy file not detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0

Scanned Files

  • .github/workflows/build.yml
  • package-lock.json

Comment thread src/lib/apiKeys.ts Fixed
CodeQL flagged the SHA-256 of an API key as an insufficient password hash.
Keys are 32 random bytes, but scrypt is the password-grade choice and keeps
a leaked table useless even against a weaker key.

Hashing now costs a few milliseconds, so it is bounded: a key without the
exact format (mskp_ and 43 base64url characters) is turned down before
hashing, and an address is stopped for ten minutes after ten wrong keys.
The hash runs off the main thread.
@Musiker15
Musiker15 merged commit f798ecb into main Oct 30, 2026
8 checks passed
@Musiker15
Musiker15 deleted the feat/spam-protection branch October 30, 2026 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants