Skip to content

Feat security captcha contract#111

Open
dominikletica wants to merge 70 commits into
feat-securityfrom
feat-security-captcha-contract
Open

Feat security captcha contract#111
dominikletica wants to merge 70 commits into
feat-securityfrom
feat-security-captcha-contract

Conversation

@dominikletica

@dominikletica dominikletica commented Jun 21, 2026

Copy link
Copy Markdown
Member

Summary

This PR is the second part of the security/extension branch split. The first part moved the large Packages -> Extensions rename and related groundwork out of the way; this branch focuses on the extension runtime/captcha contract layer.

It adds the generic extension runtime foundation needed for self-contained providers and future community extensions:

  • typed runtime/activation contribution factories, runtime boot contributions, active extension class loading, provider contributions, and public extension event listeners
  • captcha provider bridge contracts, captcha field rendering/instance guarding, server-owned captcha submit results, recovery-login captcha gating, and provider failure diagnostics/signals
  • service-backed extension facades for cache, settings, files/assets, URLs, HTTP, logs, alerts, references/entities, database CRUD, durable storage, uploads, request snapshots, content reads, permissions, CSRF, cookies, translations, vendor package loading, and a mail stub
  • extension operation queue contributions with visible operations / scheduler-tasks scopes
  • additive extension database table updates and stricter extension identity/capability scope validation
  • documentation, drafts, worklog, class map, and translation alignment for the new extension contract surface

Testing

  • bin/phpunit: Passed locally. OK, Tests: 1993, Assertions: 12334.
  • bin/jstest: Passed locally. 37 Node tests passed.
  • bin/lint: Passed locally. Includes PHP syntax, container lint, Twig syntax, UX icon refs, YAML/translations, JS syntax, JSON syntax, Markdown parse, Tailwind build, translation source key checks, and Git diff whitespace.
  • Other: php bin/console lint:container passed.
  • Other: focused render smoke for /user/login in APP_ENV=test passed and confirmed login stays captcha-free. Dev render attempts depend on local DB state; /user/register render proof was limited by local SQLite/dev DB state, while Twig syntax is covered by full lint.

Documentation

  • Updated project readme (README.md) - not applicable for this slice; extension-facing behavior is documented in drafts/manuals instead.
  • Updated feature drafts (dev/draft/*.md)
  • Updated class map (dev/CLASSMAP.md)
  • Updated worklog (dev/WORKLOG.md)
  • Updated dev/user manuals (dev/manual/*.md / docs/*.md) - dev manuals updated; user docs not applicable for this developer/runtime contract slice.

Additional Checks

  • Security/privacy considerations, public entry points, sessions, secrets, and browser storage reviewed
  • Extension boundaries, access levels, route/API/live endpoint scopes, and collision risks reviewed
  • Setup/init/CI, cross-platform behavior, disabled-feature fallbacks, and process/env handling reviewed
  • Project-rules-, architecture-, naming- and documentation-drift reviewed
  • Codebase readability, naming, hierarchy, class map, frontend structure, and test-suite clarity reviewed
  • Follow-up tasks captured in WORKLOG
  • Updated / aligned translations and user-facing copy

Linked Issues / Discussions

Part of the feat-security branch tree (#102).

Review Notes

  • Extension PHP remains policy-constrained for raw superglobals, direct process/filesystem/network calls, and ZIP/archive handling. Official extension access is routed through service-backed facades.
  • Captcha challenge generation and provider-specific UI/JS remain out of scope for this branch and belong to the concrete provider extension, such as a future IconCaptcha package.
  • extension_mail() is intentionally a development-stub facade until the real configurable mail workflow contract lands.
  • Destructive extension database migrations remain a documented follow-up; this branch supports additive updates only.
  • branding extensions are documented as a future isolated slice, not implemented here.
  • Some extension runtime coordinator classes still exceed the preferred 300-line soft target. This is already captured as a Worklog follow-up and was kept out of this slice to avoid destabilizing the contract work.

@dominikletica dominikletica self-assigned this Jun 21, 2026
@dominikletica dominikletica added enhancement New feature or request security Something is affecting security or data safety labels Jun 21, 2026

@dominikletica dominikletica left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Progress

  • Continue after branch split
  • Feature implementation branch.
  • Checks passed: bin/phpunit, bin/jstest, bin/lint.
  • Ready for local review.

@dominikletica dominikletica left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Progress

  • Addressed local review findings
  • Local review reported 12 issues: 11 resolved, 1 rejected
  • Checks passed: bin/phpunit, bin/jstest, bin/lint.
  • Ready for review.
  • Review note: Rejected the read-only extension loader finding because runtime faulting is intentional health enforcement: any main request may move broken active extension code out of runtime through the central loader; Admin read paths must only use that shared boundary and add no separate read-only mutations.

@dominikletica
dominikletica marked this pull request as ready for review June 22, 2026 00:39

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a309e419bf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Core/Extension/ExtensionContributionReader.php Outdated
Comment thread src/Security/Captcha/CaptchaRecoveryLoginSubscriber.php
Comment thread src/Controller/UserRegistrationController.php
Comment thread src/Core/Extension/ExtensionVendorFacade.php Outdated
Comment thread src/Core/Extension/Database/ExtensionDatabaseSchemaSynchronizer.php Outdated

@dominikletica dominikletica left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Progress

  • Addressed review findings
  • Local review reported 12 issues: 11 resolved, 1 rejected
  • Codex Cloud Review reported 5 issues: 3 resolved, 1 rejected, 1 handled by explicit product/policy decision
  • Checks passed: bin/phpunit, bin/jstest, bin/lint.
  • Ready for review.
  • Review note: Rejected verified-only captcha for ban recovery because captcha providers are optional and owners must not be locked out when no provider is active. Extension-local Composer/vendor support was removed by policy for early releases because package-version conflicts cannot be safely isolated across active extensions in one PHP runtime.

@dominikletica dominikletica left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Progress

  • Addressed review findings
  • Local review reported 16 issues: 13 resolved, 1 rejected, 2 handled by explicit product/policy decision.
  • Codex Cloud Review reported 5 issues: 3 resolved, 1 rejected, 1 handled by explicit product/policy decision.
  • Checks passed: bin/phpunit, bin/jstest, bin/lint.
  • Review notes:
    • Extension runtime helpers attribute the currently executing extension-owned code, not the upstream caller. Delegating to another extension's public callable is an intentional dependency/integration feature; the callee remains responsible for validating its public API inputs.
    • Captcha providers are optional. In the no-provider state, recovery must remain possible and must not lock owners out. Captcha recovery requires the active provider when one exists, but accepts the documented skipped fallback when no provider is available.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62a6d10c21

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +93 to +96
return $this->reference($this->entityManager->getRepository(ContentItem::class)->findOneBy([
'slug' => $identifier,
'status' => ContentStatus::Published,
]), $referenceOptions);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope content helper slug lookups to the routed item

When a site has two published content items with the same slug under different parents, which is allowed by the parent_uid, slug uniqueness model, this plain findOneBy() can return an arbitrary matching row. In that case extension_content_get('child') may expose fields for the wrong nested page or miss the intended root page; use the rooted content repository lookup or require callers to pass a path/parent for non-root content.

Useful? React with 👍 / 👎.

@dominikletica dominikletica Jun 22, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decision: support a nested parent/child hierarchic notation and return a contextual failure (distinguishable from "no result") when the string matches more than one entities.
Lookup-functions that list entities must also return the complete hierarchy for every content entity instead of just the sole slug.

return $this->lookupContentRoute($identifier, $options);
}

return $this->contentReference($this->findOneBy(ContentItem::class, ['slug' => $identifier]), $options);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not resolve content references by unscoped slug

For extension_lookup('content', $slug), this fetches the first row with that slug even though slugs are only unique per parent. If a draft/private or nested item shares the slug with a visible routed page, the reference lookup can return null or the wrong content reference depending on database order; resolve root slugs through the content repository/path lookup or require an unambiguous path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decision: Same as the previous issue - support nested parent/child notation and return a distinguishable failure when the string matches more than one entities.

Comment on lines +65 to +66
if (!$lock->acquire()) {
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Release captcha locks only after acquiring them

When two submissions reuse the same captcha instance, or the lock backend otherwise refuses this nonblocking acquire, this branch returns from inside the try while the finally still calls release() on a lock this request never acquired. Symfony locks can fail release when another owner still holds the resource, so the request guard can turn an expected failed captcha into a 500; track whether acquire() succeeded before releasing.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request security Something is affecting security or data safety

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant