Harden KantSites after the security review

- The installer only opens with a per-installation key that install.php
  creates and shows; the key is deleted with the first account.
- Lock files keep .env, the database, backups and logs out of reach when a
  domain points at the project folder instead of public/.
- Linked sections render by their own schema version, so an old section
  cannot carry unfiltered HTML into a newer page.
- Editors no longer move, trash or change areas and post data of published
  pages; a scheduled publication goes live with the revision that was
  scheduled (migration 20261012000100).
- Form stamps are bound to their form, IPv6 senders count per /64, and a
  flood per form or overall stops storing with a visible message.
- The consent redirect rejects control characters, update targets must look
  like 1.2.3, and own CSS cannot reach outside its block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5.5 committed 2026-10-12 14:38:25 +02:00
1 parent 14e7a3b310
commit ad7c333215
43 files changed
+810 -53

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
# Dieser Ordner gehoert nicht ins Netz (ADR 116). Zeigt die Domain richtig auf public/, wird diese
# Datei nie gelesen; zeigt sie versehentlich auf den Projektordner, sperrt sie hier alles.
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20261012000100 extends AbstractMigration
{
public function getDescription(): string
{
return 'Geplante Veröffentlichung: welche Fassung zum Termin online geht (ADR 116).';
}
public function isTransactional(): bool
{
return false;
}
public function up(Schema $schema): void
{
// Bisher ging zum Termin der jeweils neueste Entwurf online - auch einer, den nach dem
// Planen jemand ohne Recht zum Veröffentlichen gespeichert hatte. Jetzt merkt sich die
// Planung die Fassung, die beim Planen vorlag. Eine Spalte, kein Tabellenneubau.
$schema->getTable('page')->addColumn('publish_revision_id', 'string', ['length' => 32, 'notnull' => false, 'default' => null]);
}
public function down(Schema $schema): void
{
$schema->getTable('page')->dropColumn('publish_revision_id');
}
}