, backticks, `>` and `|` in ref names — only\nspaces and a short list of metacharacters are rejected. Two places in the DDEV\nsetup spliced a ref-derived value straight into an interpreter string:\n\n- `config.yaml` post-start hook interpolates `$BRANCH` into the comma\n[…]\nnch names (`main`,\n`fix/security-scan-findings`, `release/1.2.0`, `feature/TICKET-123-desc`,\n`chore/typo3-14.3`) pass through byte-identical.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(ddev): treat git ref names as untrusted in the local dev tooling", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T15:33:10Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "354520fce765f16ebbae017209b71fcd27176a9b", "body": "…o Mode, token expiry, enumeration oracles) (#93)\n\nFixes the nine findings from a Claude Security scan of `58f02dc` (whole\nrepository, medium effort, three-verifier panel per finding). One commit\nper root cause; each was reproduced against the pre-fix code before the\nfix landed.\n\n## Findings\n\n| # | \n[…]\nwhere\nscheduling noise decides the last milliseconds.\n\nNote: Copilot could not review this PR (quota limit), so the\n`copilot_code_review` ruleset is satisfied by a review event containing\nno analysis.", "is_bot": false, "headline": "fix: address nine security-scan findings (switch-user escalation, Sud…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:43:23Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "e6ad29b7739503213ba9807ed8437f31367631e2", "body": "The floor is what proves the padding works — neither branch may answer before the\nbudget. The 2x ceiling added nothing to that and would fail on a runner where\nscheduling noise, not the controller, decides the last milliseconds. It is now a\n2-second sanity guard that still catches a grossly wrong sleep.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "test: loosen the timing ceiling so a loaded runner cannot flake it", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:32:28Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "02a33ec5baf12dd1067cc5252b7668947828c022", "body": "The switch-user guard and the login-token expiry check each added exit points to\nmethods that were already at the limit, so both exceeded the three-return\nthreshold (php:S1142).\n\nresolvePasskeyToken() now reads as its three decisions -- no token, rejected\ntoken, accepted token -- with cache access a\n[…]\negistrationOptionsAction() keeps its authorization guards and delegates option\ngeneration to buildRegistrationOptions(). No behaviour change.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "refactor: extract the token-resolution and registration-option steps", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:22:25Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "9e11f50ae60dc679e6a59a93bc3583a6d0b227c9", "body": "SonarCloud reads `require` on a value-returning config file as a bug\n(php:S2003), which pushed the PR's new-code reliability rating to 2 and failed\nthe quality gate. `require_once` cannot be swapped in blindly: it yields `true`\ninstead of the returned array on a second load. AjaxRoutesTest now inclu\n[…]\nross-branch coupling, and still fails on a one-sided delay.\nAlso uses assertNotSame() where an inverted assertTrue() was flagged (php:S5785).\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "test: include config files once per process and assert timing per branch", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:22:15Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "624addedfa95ffefc6fc6bb514112771ca75b5e4", "body": "…imeException\n\nWebauthn\\Exception\\InvalidDataException extends \\Exception, so a structurally\ninvalid assertion object escaped the catch in verifyAndIssueToken(): an\nunauthenticated caller with a valid challenge token could turn the intended 401\nJSON into an uncaught-exception 500, which also skipped\n[…]\nneric AUTH_FAILED 401 with the\nbookkeeping intact; the exception class is logged so a genuine defect is still\ndiagnosable from the audit log.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(login): catch every assertion-verification failure, not just Runt…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:15:01Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "3f0d0bb3e87cc05b0afcec38d5bcf50ddfdd5e50", "body": "The unknown-username branch of /passkeys/login/options slept 50-150ms while the\nknown-username branch returned in a few milliseconds. Applying a delay to one\nbranch only does not normalize timing: it creates a large, reliably measurable\noracle. Two or three samples of the minimum round-trip separate\n[…]\ncumented that padding assumes the real work stays under budget, so per-IP rate\nlimiting remains the primary control against bulk enumeration.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(login): pad both login-options branches to one timing budget", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:12:45Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "e3bfbd384404e7929bb4601c89f754b0ca681c62", "body": "The decoy allowCredentials meant to hide whether a username exists were\nrecognisable on sight: always exactly one descriptor, always a full 32-byte HMAC\nid (43 base64url chars, a length real authenticators rarely emit), always with\nempty transports. A single request to the public login-options endpo\n[…]\nuser pressing the passkey button gets a WebAuthn prompt that cannot succeed\nrather than an immediate rejection; password login is unaffected.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(login): make decoy credentials indistinguishable from real ones", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:10:04Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "dfc23491282edc70649e02e771761452b238c7b1", "body": "The loginToken issued by /passkeys/login/verify is a bearer credential: the auth\nservice accepts it in userident and authenticates the mapped backend user without\nany WebAuthn ceremony. Its 120-second bound was delegated entirely to the cache\nTTL, so on a backend that ignores lifetimes an issued-but\n[…]\nmpt rather than lingering.\nTokens in the old bare-uid format are rejected, so a token issued just before an\nupgrade requires one fresh login.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(login): enforce login-token expiry in the token value", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:06:36Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "9ae4db8cbf0afbc2c73d54747e77b24a6e662ed6", "body": "SimpleFileBackend discards the lifetime passed to set(), never checks expiry in\nget(), and its collectGarbage() is empty. The nonce cache holds challenge nonces\nand the single-use login tokens that authenticate a backend user, so with that\nbackend an issued-but-unredeemed token stayed valid indefini\n[…]\nnts\noverriding the backend (Redis, database) are unaffected: the assignment still\nuses ??=. Documentation updated to name the actual backend.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(cache): default the nonce cache to a TTL-honouring backend", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:03:50Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "2a16e22dc3b0b58670f7361461681423f90f108c", "body": "The JS attached sudoModeInterceptor and README/Documentation stated that write\noperations require password re-verification, but no route carried the sudoMode\noption. RouteDispatcher::assertSudoMode() returns immediately in that case, so\nthe server never issued the 422 challenge the interceptor react\n[…]\ne's mfa route and the 15-minute grant the README documents. Read\nroutes stay ungated so a list request does not prompt on every panel render.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(routes): declare Sudo Mode on every passkey write route", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T04:00:35Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "99371653c87e5b47d6c223b6849913a6919cf1ea", "body": "An admin impersonating another backend user could register a passkey on the\nimpersonated account: registrationVerifyAction() bound the credential to\n$GLOBALS['BE_USER']->user['uid'], which is the impersonated user in switch-user\nmode. Core's SwitchUserController only requires isAdmin(), so a non-mai\n[…]\nring core's MfaSetupController. The setup interstitial passes switch-user\nsessions through, since registration there would now be a dead end.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "fix(management): refuse passkey writes in switch-user mode", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-30T03:57:26Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "58f02dc4e7fd3791f2602648061f24e25903cdcc", "body": "…oint) (#92)\n\n## Summary\n\nConsolidates the duplicated passkey dashboard widgets into a single,\nunified Core widget set owned by `nr_passkeys_be`. Previously both\n`nr_passkeys_be` and `nr_passkeys_fe` registered a near-identical\nadoption + credentials widget pair into the same `nrpasskeys` group, so\n\n[…]\nis repo\n(`.github/workflows/ci.yml` `run-rector: false`, no `Build/rector.php`),\nso no Rector run applies.\n\n## Scope\n\nBackend only — `nr_passkeys_fe` and the demo are untouched and handled\nseparately.", "is_bot": false, "headline": "feat(dashboard): unify passkey dashboard widgets (backend extension p…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-24T08:37:17Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "ce400197ae7ea79298a1c6209001e9bd9824060c", "body": "Reduce SonarCloud new-code duplication below the 3% gate:\n\n- Extract the fake audience-stats provider mock into a shared\n AdoptionStatsProviderMockTrait, used by both data-provider tests instead\n of each carrying an identical private helper.\n- Drop the verbatim NullBackend cache-configuration bloc\n[…]\nce/rate-limit caches, so the ext_localconf.php file-backend\n defaults are sufficient.\n\nNo production code changes; behaviour is unchanged.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "test: dedupe new dashboard-widget test arrangement", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-24T08:23:17Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "8e693eada8af15ceb8b889f49138553e64e94b2f", "body": "Replace the backend-only dashboard data providers with a segmented,\ncross-extension design so nr_passkeys_be owns a single Core widget set\nthat nr_passkeys_fe contributes to, instead of each extension shipping a\nnear-identical widget pair (four widgets total when both are installed).\n\n- Add public e\n[…]\nacements of the old nrpasskeysbe-* widgets; acceptable for a 0.x minor.\n\nBumps the extension to 0.12.0 (ext_emconf, guides.xml, changelog).\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "feat(dashboard): consolidate passkey widgets into one unified widget set", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-24T08:12:33Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "24abb27bf775d8187532c61ec2ede44d797ff3f4", "body": "Release v0.11.0 — new features since v0.10.3:\n- Conditional UI (autofill) for the backend passkey login (#88)\n- AJAX verify + single-use login token + WebAuthn Signal API (#90)\n\nVersion bumped in ext_emconf.php + Documentation/guides.xml.", "is_bot": false, "headline": "chore: release v0.11.0 (#91)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T18:55:54Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "12cbb58a2420a0c0cf6b30730d3f379926f0b8a3", "body": "Signed-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "chore: release v0.11.0", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T18:50:34Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "cd60a4fdcb70d35b3d037ce72ca6637ef3bd4c23", "body": "Replaces #89 (auto-closed when its stacked base branch #88 was\nmerged/deleted). Rebased onto main; same changes, no conditional-UI\ncommit (that landed via #88).\n\nThe backend passkey login now goes through `/passkeys/login/verify`,\nwhich runs the ceremony once and issues a **single-use login token** \n[…]\nerable/token/reason coverage + JS signal guard; PHPStan L10, CGL,\nSonarCloud, functional + E2E matrix.\n\nPart 3 of the passkey-UX hardening set (1: #88 merged, 2: FE\nnetresearch/t3x-nr-passkeys-fe#32).", "is_bot": false, "headline": "feat(login): AJAX verify + login token + WebAuthn Signal API (BE) (#90)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T16:42:10Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "7d42cb1062537c0dd1903429416cec5577ff83fd", "body": "- signalUnknownCredential: fire-and-forget via a void async IIFE (awaited call +\n local catch, no promise floating in a try) — clears the S4822 reliability bug.\n- Extract verifyAndIssueToken() shared by the discoverable and username-first\n verify paths: removes the duplicated verify/record/token block and drops\n verifyDiscoverable below the 3-return threshold.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "refactor(login): clear remaining SonarCloud reliability + return-count", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T16:34:45Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "aebd14bd4c85d513344d719081588d9e7c0a37b3", "body": "- signalUnknownCredential: use Promise.resolve().catch() (no floating promise /\n Promise-in-boolean) — clears the two reliability bugs that failed the gate.\n- LoginController: extract the duplicated 'Authentication failed' literal into a\n named constant.\n- resolvePasskeyToken / extractLoginToken: reduce early-return count.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "refactor(login): fix SonarCloud reliability + smells on the verify flow", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T16:34:45Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "3d8794cdccf1505b078865f25bc2750db1e2de78", "body": "Route the backend passkey login through the /passkeys/login/verify endpoint\ninstead of re-verifying the raw assertion in the auth service. The endpoint runs\nthe WebAuthn ceremony once and, on success, issues a single-use login token\n(120s TTL) that the JS submits through the standard login form; the\n[…]\nerify token + discoverable unknown-credential reason + auth token\npath), JS (signal guard). Full unit suite green, CGL + PHPStan L10 clean.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "feat(login): AJAX verify + login token + WebAuthn Signal API", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T16:34:45Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "cfee45783bb6344ad53b0f285027d7c40986ebbd", "body": "…(#88)\n\nSurfaces discoverable passkeys directly in the standard backend login\nform's **username-field autofill** (Conditional UI / `mediation:\n'conditional'`), so returning users don't have to click the passkey\nbutton. Ports the pattern the frontend extension already uses.\n\n**Behaviour**\n- Feature-d\n[…]\nverage for the\nautocomplete-merge logic); the full ceremony is validated like the\nbutton flow (E2E / virtual authenticator).\n\nPart 1 of a passkey-UX hardening set (2: FE Signal API, 3: BE Signal\nAPI).", "is_bot": false, "headline": "feat(login): conditional UI (autofill) for the backend passkey login …", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T16:33:47Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "d7585eaaa09bd6050aafe0707c4158c91ef80c37", "body": "Surface discoverable passkeys directly in the standard TYPO3 backend login\nform's username-field autofill, so a returning user no longer has to click the\n\"Sign in with a passkey\" button. On init, when discoverable login is enabled and\nthe browser supports conditional mediation, the field gets\n`autoc\n[…]\nan explicit button click\n aborts the pending conditional ceremony first.\n- autocomplete token is merged, not clobbered, and de-duplicated.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "feat(login): conditional UI (autofill) for the backend passkey login", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-23T13:27:08Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "ab8fe632d76c723bd47fa2a13fc4658ebf678170", "body": "## What was broken\n\n`Documentation/guides.xml` had a **corrupted XML declaration** — the\nversion attribute had been rewritten from `1.0` to `0.10.2` (`\u003c?xml\nversion=\"0.10.2\" encoding=\"UTF-8\"?>`), which is not a valid XML version.\nThe `\u003cproject>` `version`/`release` attributes were also stale\n(`0.10.\n[…]\n--schema guides.xsd Documentation/guides.xml` —\nschema-valid ✅\n- `render-guides:latest configure Documentation` — exit 0 ✅\n- `render-guides:latest render …` — exit 0 (18 documents) ✅\n\nRelease v0.10.3.", "is_bot": false, "headline": "fix(docs): repair guides.xml and add docs render CI (#87)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-22T05:55:29Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "1d25eee79038e470c97ab6a5d555b584ea485df7", "body": "The XML declaration had been corrupted to version=\"0.10.2\" (invalid; must be \"1.0\") and the \u003cproject> version/release were stale (0.10.1), which made the TYPO3 guides render fatal at the strict 'configure' step. Adds the docs render job so CI catches guides.xml errors. Release v0.10.3.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(docs): repair guides.xml and add docs render CI", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T15:45:40Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "5384ce7f1febe404a58a5787e0314912a876343b", "body": "Patch release: v14 3-color icon rework + unified palette, dark-mode\nfixes for interstitial/banner/login divider\n([#84](https://github.com/netresearch/t3x-nr-passkeys-be/pull/84)), and\ntwo admin-only dashboard widgets nrpasskeysbe-adoption +\nnrpasskeysbe-credentials\n([#85](https://github.com/netresearch/t3x-nr-passkeys-be/pull/85)).\nAdditive/non-breaking 0.x patch by policy.", "is_bot": false, "headline": "chore: release v0.10.2 (#86)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T15:05:43Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "dd2cac9eb1eebb5e9e01cd34007594a107e0f8c6", "body": "Signed-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "chore: release v0.10.2", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T14:41:11Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "7845b2eddcda9adec7b149a45c3e28448fa5fa4a", "body": "## Problem\n\n1. **Code examples unreadable in dark mode** (reported): on\n`/typo3/module/admin/vault` (TYPO3 14.3), the code examples in the\nOverview/Help templates rendered in the core magenta inline-code color.\nRoot cause: TYPO3 v14 forces `code { color: var(--typo3-text-color-code)\n!important }` on\n[…]\nsabled users are not counted (gemini-code-assist\nfinding). Includes new functional tests (real DB) for the join and for\nthe aggregate/getStats() consistency. CI re-ran fully green on the\nupdated head.", "is_bot": false, "headline": "feat(dashboard): add passkey adoption and credential-count widgets (#85)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T14:39:25Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "14cb31a6086b59effc0a292293a019d07fb419a3", "body": "… surfaces (#84)\n\n## Problem\n\n**Icons** — the four SVGs mixed three drifted teals\n(`#2999a4`/`#2d99a4`/`#2F99A4`) and inconsistent styles:\n`ModuleIcon.svg` mixed hardcoded teal paths with `currentColor`,\n`Extension.svg` was the generic Netresearch corporate logo with a\nfragile embedded `\u003cstyle>` blo\n[…]\ns://docs.typo3.org/permalink/t3coreapi:icon-registration),\n[color scheme user\nsetting](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/13.3/Feature-104607-IntroduceColorSchemeSwitch.html)", "is_bot": false, "headline": "feat: v14 three-color icons and light/dark scheme support for backend…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T14:39:18Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "8e543f2f00b8c40511c5bb1e77885d07de0ac5a0", "body": "Join be_users in countActiveCredentials() so leftover active credentials\nof soft-deleted or disabled backend users no longer inflate the\n\"Active passkeys\" widget number. Adds a functional test proving the\njoin against a real database and pins the aggregate counts to the\ngetStats() results.\n\nThe pre-\n[…]\ngning it would change shipped admin-module numbers and is out of\nscope for this feature PR.\n\nAddresses gemini-code-assist review on PR #85.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(dashboard): count only credentials of active backend users", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T12:30:00Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "a6d04a6e0fadf0e51c10272d678851200b880c74", "body": "Register two admin-only TYPO3 dashboard widgets in a new \"Passkeys\"\nwidget group (nrpasskeys):\n\n- nrpasskeysbe-adoption: core DoughnutChartWidget showing active backend\n users with vs. without passkeys\n- nrpasskeysbe-credentials: core NumberWithIconWidget showing the total\n of active (not revoked)\n[…]\nre excluded from\nPHPStan analysis because the missing interface causes an unignorable\ninternal reflection error on the v12/v13 matrix legs.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "feat(dashboard): add passkey adoption and credential-count widgets", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T12:22:41Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "6483bbbe42ae7581f20a479893656afbdae76dd0", "body": "Extract a renderInterstitialBody() helper for the two color-scheme\ntests so the shared Required-enforcement boilerplate is not duplicated\n(SonarCloud new-code duplication gate).\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "refactor(tests): deduplicate interstitial scheme test setup", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:34:58Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "87a297c887a32d098b2fd102cbe3a11b3e75f72f", "body": "Three surfaces hardcoded palette colors and broke in one of the TYPO3\nv14 color schemes:\n\n- Setup interstitial: was permanently dark (#1e1e1e page with an\n off-brand #0078d4 button) regardless of scheme. The page now declares\n color-scheme: light dark with a light default palette, dark values\n vi\n[…]\nles to classes in the new\nResources/Public/Css/backend.css, loaded by both event listeners\n(CSP-friendly, no functional change on v12/v13).\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(ui): make backend surfaces adapt to the v14 light/dark schemes", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:29:25Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "2b4c25cf28b096f05581a9a7d4842e9afe23045d", "body": "Redraw the four extension icons as a consistent person+key motif and\nunify the three drifted teals (#2999a4/#2d99a4/#2F99A4) to the single\nbrand value #2F99A4:\n\n- ModuleIcon.svg: v14 three-color spec - currentColor primary glyph,\n 40%-opacity secondary detail, single brand-teal accent on the key vi\n[…]\n\n\nThe v14/legacy runtime switch in Configuration/Icons.php and both icon\nidentifiers (passkeys-be-login, passkeys-be-module) are unchanged.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "feat(icons): redraw all icons to the TYPO3 v14 three-color icon style", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:29:11Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "ae9e4818fdadca1f233927137c32021900da23a9", "body": "Patch release shipping\n[#82](https://github.com/netresearch/t3x-nr-passkeys-be/pull/82): the\npasskey audit log moves from the web-reachable, often-unwritable\n`typo3temp/var/log/` to `Environment::getVarPath()/log/` — an unwritable\nFileWriter throws #1321804422 on every warning-logging request (took the\ntypo3-demo backend down). Also catches `Documentation/guides.xml` up\nfrom 0.9.4 (missed in the v0.10.0 bump).", "is_bot": false, "headline": "chore: release v0.10.1 (#83)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:16:06Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "f26fc06f03146d9ecf4e0d8540497c8b8cef712f", "body": "Signed-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "chore: release v0.10.1", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:08:46Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "40c792a6828e3d2be341bec80c2e59925b6180bb", "body": "## Summary\n\nThe audit-log FileWriter used the relative path\n`typo3temp/var/log/passkey_auth.log`, which resolves **below the public\nweb root**. Two problems:\n\n1. **Availability**: in containerized deployments the directory is often\nnot writable by the PHP user, or the file gets created root-owned by\n[…]\nr ci:test:php:cgl` ✔, `ci:test:php:phpstan` ✔ (level 10),\n`ci:test:php:unit` ✔ (598 tests).\n- After release + demo redeploy: `/typo3/` serves 200 again and warnings\nland in `var/log/passkey_auth.log`.", "is_bot": false, "headline": "fix(log): write passkey audit log to the project var path (#82)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:08:32Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "0ec5f7fbd2c085eb7480733ea7d7222ac1552a2d", "body": "The FileWriter was configured with the relative path\ntypo3temp/var/log/passkey_auth.log, which resolves below the public web\nroot. In containerized deployments that directory is frequently not\nwritable by the PHP user (or the file gets created root-owned by CLI\nruns), and an unwritable FileWriter th\n[…]\nr a security audit log.\n\nUse Environment::getVarPath() . '/log/passkey_auth.log' instead, the\ncanonical writable location in composer mode.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(log): write passkey audit log to the project var path", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-07-21T11:04:07Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "4243e511e4b87c1aaeb37ad143972cd74462c479", "body": "## Release v0.10.0\n\nMinor bump: 54 commits since v0.9.4, including three new features\nalongside a security-hardening cluster.\n\n### Features\n- i18n: admin Help page fully translatable\n- i18n: dashboard onboarding infoboxes translatable\n- CLI: `passkeys:recovery` command for out-of-band recovery\n\n### \n[…]\nTooling\n- Adopted the canonical TYPO3-extension CI template; PHPStan raised to\nlevel 10\n\nBumps `ext_emconf.php` to 0.10.0. Tag `v0.10.0` will be pushed after\nmerge to trigger the TER + GitHub release.", "is_bot": false, "headline": "chore(release): v0.10.0 (#81)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T13:00:41Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "33e2ffc59c7e5a696ff69131cc031c546c0f4505", "body": "Signed-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "chore(release): v0.10.0", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T12:54:04Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "675b717f6359bd3ee86596eef908ae6227638503", "body": "…ume (#80)\n\n## Summary\n\nThe rate-limit gate split the check and the record across two separate\nlock acquisitions: `checkRateLimit()` (read + compare, release) then\nlater `recordAttempt()` (increment, release). On the live login path\nthat leaves a **check-then-record window** — N concurrent requests \n[…]\nmic increment under the\nlimit, reject at the limit without incrementing, fail-closed when the\nlock can't be acquired.\n- Local `make test-unit`: **573 passed**; `phpstan` (1G): no errors;\n`cgl`: clean.", "is_bot": false, "headline": "fix(ratelimit): close rate-limit check/record TOCTOU with atomic cons…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:52:32Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "751f14acd5f00c8892626a9875231faa5f45fa5d", "body": "…#78)\n\n## Summary\n\nThe system-maintainer privilege boundary — in TYPO3 only system\nmaintainers may manage other admins/maintainers — was enforced **only in\nthe FormEngine UI element** (`PasskeyInfoElement`), which hides the\nrevoke/unlock buttons. The admin AJAX endpoints enforced just\n`isAdmin()`:\n\n\n[…]\nmaintainer → maintainer) on `removeAction`\nand `listAction`, and allowed (maintainer → maintainer) on\n`removeAction`.\n- Local `make test-unit`: **573 passed**; `phpstan` (1G): no errors;\n`cgl`: clean.", "is_bot": false, "headline": "fix(admin): enforce system-maintainer boundary on passkey admin API (…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:52:04Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "12c41ffa3d2400d1c4f1ff7493237b919bdff7ea", "body": "… rpId/origin (#79)\n\nTwo backend hardening fixes from a security review, as **two separable\ncommits**.\n\n## 1. Narrow the interstitial AJAX exemption (`bad9b9e`)\n`PasskeySetupInterstitial` exempted any route starting with `ajax_`.\nTYPO3 registers every backend AJAX route as `ajax_\u003cid>`, so the bare\np\n[…]\nr\nsoftened to a logged warning — the first commit stands alone.\n\n## Test plan\n- New/updated unit tests in both areas.\n- Local `make test-unit`: **573 passed**; `phpstan` (1G): no errors;\n`cgl`: clean.", "is_bot": false, "headline": "fix: narrow interstitial AJAX exemption + fail closed on Host-derived…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:51:26Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "be7400faff23098952ae502ceb17c749a9e825bd", "body": "… path (#77)\n\n## Summary\n\n`discoverableLoginEnabled` was enforced only at challenge issuance\n(`LoginController::optionsAction`), not on the auth-service path that\nestablishes the session. Because a challenge token carries no mode\nbinding, a username-first token could otherwise drive the discoverable\n[…]\n lockout DoS via `recordFailure(countUserLockout: false)`. This PR is\nnow scoped to the discoverable-flag enforcement only.\n\n## Validation\nPHPStan level 10, full unit suite, and CGL all green locally.", "is_bot": false, "headline": "fix(auth): enforce discoverableLoginEnabled on the auth-service login…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:51:22Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "ec3a68d52d39fe42abad63969fb10ef0a0e18a72", "body": "Review follow-up:\n- Rename optionsActionRecordsAttempt/verifyActionRecordsAttempt (they now assert\n consumeRateLimit, not the removed recordAttempt) to ...ConsumesRateLimit.\n- Add a boundary test that consumeRateLimit allows and increments the last\n attempt at count 4 -> 5 (guards the >= vs > comp\n[…]\nontroller that consumeRateLimit counts the attempt up front,\n so a subsequently lockout-rejected request still consumes rate-limit budget.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "test(ratelimit): clarify consume-rate-limit tests and ordering", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:46:36Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "d3ba5d6c6bdded4692a482a6b1a153d8f983ac41", "body": "checkRateLimit() and recordAttempt() acquire separate locks, so the live login\npath (checkRateLimit -> ... -> recordAttempt) left a check-then-record window:\nN concurrent requests could all pass the check before any of them incremented,\novershooting the configured rate limit by the in-flight count. \n[…]\n unit tests for consumeRateLimit (atomic increment under limit, reject at\nlimit without incrementing, fail-closed on lock-acquire failure).\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(ratelimit): close rate-limit check/record TOCTOU with atomic consume", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:46:36Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "cea15b138a113039816abebebec777f1781c3fdc", "body": "…esent\n\nAddresses Copilot review: assertHostTrustEnforced() previously fired in getEffectiveRpId/getEffectiveOrigin even on the CLI/cron path where the Host header is empty and the 'localhost' fallback is a safe anchor, throwing spuriously. Now the trust check runs only when a non-empty request Host would actually become the WebAuthn anchor.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(config): only enforce host-trust when a request Host header is pr…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:44:04Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "4feefc6c2a0efde34e988260391b293b252a63bf", "body": "Review follow-ups:\n- Correct the assertHostTrustEnforced() docblock/comment: an empty\n trustedHostsPattern is treated by TYPO3 core as invalid and rejects every\n Host (fail-closed), not \"accept any Host\"; only '.*' is allow-all. The guard\n still refuses to derive an anchor from either, now accura\n[…]\nut/\n ajax_mfa exemptions, so the narrowed allowlist is regression-protected for the\n auth/logout/MFA routes an enforced user still needs.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "test+docs: review follow-ups for host-binding and interstitial scope", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:41:15Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "b7a23b6c95a5ccead4af1aaac1320e7c139cda5d", "body": "…Host\n\nWhen rpId/origin are left empty they are derived from the request Host header.\nThat value is only trustworthy when TYPO3's host-header validation is enforcing\na pattern; the framework treats both '' and the allow-all '.*' trustedHostsPattern\nas \"accept any Host\", which makes the derived WebAu\n[…]\nostsPattern or pins rpId/origin (the documented production\nrequirement). Pinning rpId/origin or tightening trustedHostsPattern resolves it.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(config): fail closed when deriving rpId/origin from an untrusted …", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:40:37Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "dabdde68236c85b2a590690293376248bfd5ce70", "body": "The passkey-setup interstitial exempted any route whose identifier starts with\n'ajax_'. TYPO3 core registers every backend AJAX route as 'ajax_'.\u003cid>, so the\nbare prefix exempted ~260 state-changing endpoints (ajax_record_process =\nDataHandler save, ajax_file_process, ...). An enforced-but-unenrolle\n[…]\n\nAdds a regression test asserting ajax_record_process is blocked, and repoints\nthe existing AJAX-exemption tests to real enrollment routes.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(middleware): narrow interstitial AJAX exemption to enrollment routes", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:40:37Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "4632b5c234b7b43f4577f883397ad1a77b9286b2", "body": "… path\n\ndiscoverableLoginEnabled was checked only at challenge issuance\n(LoginController::optionsAction), not on the auth-service path that establishes\nthe session. Because a challenge token carries no mode binding, a username-first\ntoken could otherwise drive the discoverable code path even when an\n[…]\ning change originally in this PR was dropped: main's ca2180a\nalready addresses the lockout DoS via recordFailure(countUserLockout: false).)\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(auth): enforce discoverableLoginEnabled on the auth-service login…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:37:18Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "17cdc931800b4df49bebefcfed64498b4ac6bafe", "body": "Addresses Copilot review (avoid the '\\intval' string callable) while keeping PHPStan level 10 happy: iterate and is_numeric-narrow each maintainer ID before the (int) cast, instead of array_map over a mixed-typed array (which level 10 rejects).\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(admin): normalize system-maintainer IDs without a string callable", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:28:56Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "6cb5f001d435464dc254628d2042279ce9dae9d6", "body": "- Extract the repeated 'Insufficient privileges to manage this user' literal\n into ERROR_INSUFFICIENT_PRIVILEGES (used by all six guards).\n- Extract setUpNonMaintainerAdminTargetingMaintainer() and assertManagementDenied()\n test helpers to remove the duplicated setup/assert blocks across the\n maintainer-boundary tests.\n\nReduces new-code duplication below the SonarCloud quality gate; behaviour\nunchanged.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "refactor(admin): dedupe maintainer-guard literal and tests (SonarCloud)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:23:14Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "4810d2939c89410c02275f02cead9c508ee84465", "body": "…dpoints\n\nReview follow-up: sendReminderAction and clearNudgeAction also act on a target\nbackend user by beUserUid (they write be_users.passkey_nudge_until), but were\nnot given the isManagementAllowedFor() guard the other four admin actions\nreceived, leaving the system-maintainer boundary inconsiste\n[…]\nr/clear-nudge plus a test that a non-maintainer\nadmin can still manage a non-maintainer target when a systemMaintainers list is\nconfigured.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(admin): extend system-maintainer guard to reminder/clear-nudge en…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:23:14Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "cb36b21eb1d03104596ae2970df33bd7510f1b1e", "body": "The system-maintainer privilege boundary (only system maintainers may manage\nother maintainers) was enforced only in the FormEngine UI element, which hides\nbuttons. The admin AJAX endpoints (list/remove/unlock/revoke-all) gated solely\non isAdmin(), so any backend admin could call them directly again\n[…]\ntainer targets are unaffected.\n\nAdds unit tests for the denied (non-maintainer to maintainer) and allowed\n(maintainer to maintainer) paths.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "fix(admin): enforce system-maintainer boundary on passkey admin API", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-25T11:23:14Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "ee41bd236a614765daa956e697d0dc41f1e26520", "body": "Migrate CI to the canonical `typo3-extension` template\n(netresearch/.github): explicit per-call-site permissions on every\nreusable, drift-enforced. Security/quality jobs (checks.yml) are\nbyte-governed; the test matrix (ci.yml) and release.yml are per-repo\n(intentional-drift, preserved from this repo).", "is_bot": false, "headline": "ci: adopt canonical typo3-extension template (#71)", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-17T13:21:59Z", "body_truncated": false, "is_coding_agent": false }, { "oid": "3b059a400c1402b41fc2c2046067baaada1e4b57", "body": "Opengrep --config auto runs python.django.security.django-no-csrf-token\nagainst all files and flags Resources/Private/Templates/Interstitial/Setup.html:34\n(\u003cform method=\"post\">). That file is a TYPO3 Fluid (.html) template, not a\nDjango template, so the rule does not apply.\n\nAdd a scoped .semgrepign\n[…]\n which invokes 'opengrep scan ... .' with a\npath arg) excluding only that single template path. Opengrep continues to\nscan all other files.\n\nSigned-off-by: Sebastian Mendel \u003cgithub@sebastianmendel.de>", "is_bot": false, "headline": "ci: ignore Django CSRF false-positive on Fluid template", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-17T12:55:46Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "fa3c3bc2d5c4fe35593bbc8f52f026fd5b3616ce", "body": "…odule (TEST-1) (#76)\n\nTEST-1 from the review (tracked in #73). The JS unit tests exercised\nre-implemented **copies** of the base64 helpers, not the shipped code.\n\n## Change\n\n- **`Util/Base64.js`** — single shared module with `base64urlToBuffer` /\n`bufferToBase64url` / `bufferToBase64`, imported by \n[…]\nss against the real\n`Util/Base64.js`, PHPStan L10 + CGL clean, `InjectPasskeyLoginFields`\nunit test green. CI runs e2e (full login ceremony) + SonarCloud\n(maintainability gate).\n\nCloses TEST-1 in #73.", "is_bot": false, "headline": "refactor(js): share WebAuthn base64 helpers + modernize login to ES m…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T20:09:45Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "643e6a68f3e8a45c80c1dd19dac3662f9f9a4121", "body": "… module (TEST-1)\n\nThe JS unit tests exercised re-implemented copies of the base64 helpers, not the\nshipped code. Extract a single Util/Base64.js imported by both passkey modules and by\nthe tests, so the tests now cover the real implementation.\n\nPasskeyManagement.js (already an ES module) imports th\n[…]\n 63 JS tests pass against the real Util/Base64.js; PHPStan L10, CGL, and\nthe InjectPasskeyLoginFields unit test green.\n\nCloses TEST-1 in #73.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "refactor(js): share WebAuthn base64 helpers, modernize login to an ES…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T20:00:51Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "2927a63a2549dd9a744c60c9a6c4a6bea2ac6104", "body": "… ceremonies (ARCH-2) (#75)\n\nARCH-2 from the review (tracked in #73). The ~560-line `WebAuthnService`\nmixed the attestation (registration) and assertion (authentication)\nceremonies plus the shared WebAuthn library plumbing in one class.\n\n## Change\n\nExtracts three focused collaborators **behind the u\n[…]\nlive TYPO3 v13.4 backend (login page\nreturns 200)\n- CI runs the functional matrix + e2e, which exercise the\n`makeInstance(WebAuthnService)` auth path and the full login ceremony\n\nCloses ARCH-2 in #73.", "is_bot": false, "headline": "refactor(service): split WebAuthnService into attestation + assertion…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T19:21:18Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "070da9665e261afbc939fb7d00de130b169832a3", "body": "…creenshots (#74)\n\nLive-backend follow-ups from the review (tracked in #73). All changes\nverified on a running TYPO3 v13.4 DDEV backend.\n\n## Changes\n\n- **Fix double-encoded ampersand** — the dashboard \"Passkeys & MFA\"\ninfobox passed `&` as the `f:be.infobox` *title* argument; the\nViewHelper re-e\n[…]\nage —\nheadings, MFA table, FAQ, links — all confirmed). Screenshots captured\nfrom the same backend. PHP/JS/PHPStan/CGL unaffected (template + XLF +\nimage changes only).\n\nPart of the #73 follow-up set.", "is_bot": false, "headline": "fix(admin): dashboard infobox rendering + i18n + real documentation s…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T19:14:08Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "5a96ed7d324fcd7b53d0aa63ed5af641ce2eb982", "body": "… ceremonies\n\nThe ~560-line WebAuthnService mixed the attestation (registration) and assertion\n(authentication) ceremonies plus the shared WebAuthn library plumbing in one class.\nExtract three focused collaborators behind the unchanged public surface:\n\n- WebAuthnCeremonyFactory: shared serializer (c\n[…]\nss, PHPStan level 10 + phpat architecture rules clean, CGL\nclean, and the DI container compiles on a live TYPO3 v13 backend (login page 200).\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "refactor(service): split WebAuthnService into attestation + assertion…", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T19:11:19Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "32840c8b1d5997bedf3cfa06693ea4f72ad464aa", "body": "Help.html (rollout guide, recovery procedures, MFA coexistence table, FAQ) was 414\nlines of hardcoded English. Extract all prose into 80 locallang.xlf trans-units via\nf:translate, keeping inline markup (\u003ccode>/\u003cem>/\u003cstrong>/\u003ca>) in the XLF source rendered\nthrough f:format.raw, and passing the dynami\n[…]\nline code/emphasis render,\nthe three Dashboard links resolve to real URLs, and the page is visually identical to\nthe previous English output.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "feat(i18n): make the admin Help page fully translatable", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T18:58:55Z", "body_truncated": true, "is_coding_agent": false }, { "oid": "11dbc50d3c83ebcbaedfac4fb7f6bbd8e92bd794", "body": "The Getting started, Configuration note, and Passkeys & MFA infoboxes on the admin\ndashboard were hardcoded English. Extract their text into locallang.xlf via f:translate,\nkeeping inline markup (\u003cstrong>/\u003cem>/\u003ccode>/\u003ca>) in the template and passing the\nauto-detected rpId as a %s argument. Verified on a live TYPO3 v13 backend: all three\ninfoboxes render identically with keys resolved, markup intact, and no raw label leakage.\n\nSigned-off-by: Sebastian Mendel \u003cinfo@sebastianmendel.de>", "is_bot": false, "headline": "feat(i18n): make dashboard onboarding infoboxes translatable", "author_name": "Sebastian Mendel", "author_login": "CybotTM", "committed_at": "2026-06-16T18:14:15Z", "body_truncated": false, "is_coding_agent": false } ], "releases_count": 22, "commits_last_year": 452, "latest_release_at": "2026-07-24T08:40:15Z", "latest_release_tag": "v0.12.0", "releases_from_tags": false, "days_since_last_push": 0, "active_weeks_last_year": 19, "days_since_latest_release": 12, "mean_days_between_releases": 6.3 }, "artifacts": { "collected": true, "structure": [ "tree.compose", "tree.dockerfile" ], "declarations": [ { "name": "netresearch/nr-passkeys-be", "path": "composer.json", "tokens": [ "composer.type:typo3-cms-extension" ], "ecosystem": "packagist" }, { "name": null, "path": "package.json", "tokens": [ "npm.private" ], "ecosystem": "npm" } ] }, "community": { "has_readme": true, "has_license": true, "readme_badges": { "hosts": [ "api.securityscorecards.dev", "codecov.io", "github.com", "shields.io", "www.bestpractices.dev" ], "total": 10, "header": 4, "collected": true, "has_inspect_badge": false }, "has_description": true, "has_contributing": true, "health_percentage": 100, "has_issue_template": false, "has_code_of_conduct": true, "has_pull_request_template": true }, "ecosystem": { "packages": [ { "name": "netresearch/nr-passkeys-be", "exists": true, "license": "GPL-2.0-or-later", "keywords": [ "Authentication", "backend", "typo3", "Passwordless", "FIDO2", "webauthn", "passkeys" ], "ecosystem": "packagist", "categories": [], "matches_repo": true, "registry_url": "https://packagist.org/packages/netresearch/nr-passkeys-be", "declared_type": "typo3-cms-extension", "is_deprecated": false, "latest_version": "v0.12.0", "repository_url": "https://github.com/netresearch/t3x-nr-passkeys-be", "versions_count": 23, "total_downloads": 5516, "dependents_count": 1, "deprecation_note": null, "maintainers_count": null, "monthly_downloads": 2161, "first_published_at": null, "latest_published_at": "2026-07-24T08:37:17Z", "latest_version_yanked": null, "days_since_latest_publish": 12 } ] }, "popularity": { "forks": 0, "stars": 3, "watchers": 1, "fork_history": { "days": [], "complete": true, "collected": 0, "total_forks": 0 }, "star_history": null, "open_issues_and_prs": 1 }, "ai_readiness": { "has_nix": false, "example_dirs": [], "has_llms_txt": false, "has_dockerfile": true, "has_mcp_signal": false, "bootstrap_files": [ "Makefile" ], "api_schema_files": [], "has_devcontainer": false, "typecheck_configs": [], "toolchain_manifests": [], "largest_source_bytes": 66621, "source_files_sampled": 144, "oversized_source_files": 1, "agent_instruction_files": [ ".ddev/AGENTS.md", ".github/copilot-instructions.md", ".github/workflows/AGENTS.md", ".github/workflows/CLAUDE.md", "AGENTS.md", "CLAUDE.md", "Classes/AGENTS.md", "Classes/CLAUDE.md", "Documentation/AGENTS.md", "Documentation/CLAUDE.md", "Resources/AGENTS.md", "Resources/CLAUDE.md", "Tests/AGENTS.md", "Tests/CLAUDE.md" ], "agent_instruction_max_bytes": 11003 }, "dependencies": { "manifests": [ "composer.json", "package.json" ], "advisories": { "error": "No resolved dependencies carried a version and a supported ecosystem", "scope": "repository_graph", "source": null, "findings": [], "collected": false, "malicious": [], "truncated": false, "by_severity": {}, "advisory_count": 0, "affected_count": 0, "assessed_count": 0, "malicious_count": 0, "assessed_package": null, "unassessed_count": 22, "direct_affected_count": 0 }, "ecosystems": [ "npm", "packagist" ], "dependencies": [ { "name": "typo3/cms-core", "manifest": "composer.json", "ecosystem": "packagist", "version_constraint": "^12.4 || ^13.4 || ^14.3" }, { "name": "typo3/cms-backend", "manifest": "composer.json", "ecosystem": "packagist", "version_constraint": "^12.4 || ^13.4 || ^14.3" }, { "name": "typo3/cms-setup", "manifest": "composer.json", "ecosystem": "packagist", "version_constraint": "^12.4 || ^13.4 || ^14.3" }, { "name": "web-auth/webauthn-lib", "manifest": "composer.json", "ecosystem": "packagist", "version_constraint": "^5.3" } ], "all_dependencies": { "error": null, "source": "github-sbom", "packages": [ { "name": "typo3/cms-backend", "direct": true, "version": null, "ecosystem": "packagist" }, { "name": "typo3/cms-core", "direct": true, "version": null, "ecosystem": "packagist" }, { "name": "typo3/cms-setup", "direct": true, "version": null, "ecosystem": "packagist" }, { "name": "web-auth/webauthn-lib", "direct": true, "version": null, "ecosystem": "packagist" }, { "name": "@playwright/test", "direct": false, "version": "^1.50", "ecosystem": "npm" }, { "name": "@vitest/coverage-v8", "direct": false, "version": "^4.0", "ecosystem": "npm" }, { "name": "jsdom", "direct": false, "version": "^29.0", "ecosystem": "npm" }, { "name": "vitest", "direct": false, "version": "^4.0", "ecosystem": "npm" }, { "name": "dg/bypass-finals", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "ergebnis/phpstan-rules", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "friendsofphp/php-cs-fixer", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "netresearch/typo3-ci-workflows", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "php", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpat/phpat", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpstan/extension-installer", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpstan/phpstan", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpstan/phpstan-deprecation-rules", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpstan/phpstan-phpunit", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpstan/phpstan-strict-rules", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "phpunit/phpunit", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "typo3/cms-dashboard", "direct": false, "version": null, "ecosystem": "packagist" }, { "name": "typo3/testing-framework", "direct": false, "version": null, "ecosystem": "packagist" } ], "collected": true, "truncated": false, "total_count": 22, "direct_count": 4, "indirect_count": 18 } }, "maintainership": { "issues": { "open_prs": 1, "merged_prs": 100, "open_issues": 0, "closed_ratio": 1, "closed_issues": 4, "closed_unmerged_prs": 5 }, "bus_factor": 1, "bot_contributors": 1, "top_contributors": [ { "type": "User", "login": "CybotTM", "commits": 443, "avatar_url": "https://avatars.githubusercontent.com/u/326348?v=4" }, { "type": "User", "login": "just-tobi", "commits": 10, "avatar_url": "https://avatars.githubusercontent.com/u/5242689?v=4" } ], "contributors_sampled": 2, "top_contributor_share": 0.978 }, "quality_signals": { "has_ci": true, "has_tests": true, "ci_workflows": [ "auto-merge-deps.yml", "check-template-drift.yml", "checks.yml", "ci.yml", "codeql.yml", "community.yml", "ddev-hardening.yml", "dependency-review.yml", "docs.yml", "labeler.yml", "pr-quality.yml", "release.yml", "republish.yml", "scorecard.yml" ], "has_docs_dir": true, "linter_configs": [ ".php-cs-fixer.php", "phpstan.neon" ], "has_editorconfig": true, "has_linter_config": true, "has_precommit_config": false }, "security_signals": { "lockfiles": [], "scorecard": { "checks": [ { "name": "Binary-Artifacts", "score": 10, "reason": "no binaries found in the repo", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts" }, { "name": "Branch-Protection", "score": 4, "reason": "branch protection is not maximal on development and all release branches", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection" }, { "name": "CI-Tests", "score": 10, "reason": "13 out of 13 merged PRs checked by a CI test -- score normalized to 10", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests" }, { "name": "CII-Best-Practices", "score": 7, "reason": "badge detected: Silver", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices" }, { "name": "Code-Review", "score": 10, "reason": "all changesets reviewed", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review" }, { "name": "Contributors", "score": 10, "reason": "project has 3 contributing companies or organizations -- score normalized to 10", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors" }, { "name": "Dangerous-Workflow", "score": null, "reason": "no workflows found", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow" }, { "name": "Dependency-Update-Tool", "score": 10, "reason": "update tool detected", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool" }, { "name": "Fuzzing", "score": 0, "reason": "project is not fuzzed", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing" }, { "name": "License", "score": 10, "reason": "license file detected", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license" }, { "name": "Maintained", "score": 10, "reason": "30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained" }, { "name": "Packaging", "score": null, "reason": "packaging workflow not detected", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging" }, { "name": "Pinned-Dependencies", "score": null, "reason": "no dependencies found", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies" }, { "name": "SAST", "score": 10, "reason": "SAST tool is run on all commits", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast" }, { "name": "Security-Policy", "score": 10, "reason": "security policy file detected", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy" }, { "name": "Signed-Releases", "score": 8, "reason": "5 out of the last 5 releases have a total of 5 signed artifacts.", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases" }, { "name": "Token-Permissions", "score": null, "reason": "No tokens found", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions" }, { "name": "Vulnerabilities", "score": 10, "reason": "0 existing vulnerabilities detected", "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities" } ], "commit": "f23f92063078d5272ef1206c9ae1ee93539b298f", "ran_at": "2026-08-05T20:22:04Z", "aggregate_score": 8.5, "scorecard_version": "v5.5.0" }, "has_codeql_workflow": true, "has_security_policy": true, "has_dependabot_config": true }, "contribution_flow": { "collected": true, "recent_prs": { "merged_7d": 17, "decided_7d": 17, "merged_30d": 27, "authors_30d": 1, "decided_30d": 28, "sample_size": 60, "window_days": 30, "sample_exhausted": false, "authors_probed_30d": 1, "newcomer_merged_30d": 0, "bot_prs_excluded_30d": 0, "newcomer_authors_30d": 0, "newcomer_decided_30d": 0 }, "ci_last_run_at": "2026-08-05T17:39:08Z", "oldest_open_prs": [ { "number": 108, "created_at": "2026-08-05T08:30:14Z", "last_comment_at": "2026-08-05T20:12:13Z", "last_comment_author": "sonarqubecloud" } ], "last_merged_pr_at": "2026-08-05T17:18:36Z", "ci_last_conclusion": "SUCCESS", "oldest_open_issues": [] } }, "config": { "disabled_metrics": [], "disabled_categories": [], "disabled_components": {} }, "source": { "url": "https://github.com/netresearch/t3x-nr-passkeys-be", "host": "github.com", "name": "t3x-nr-passkeys-be", "owner": "netresearch" }, "metrics": { "overall": { "key": "overall", "band": "excellent", "name": "Overall health", "note": "The weighted overall 74 is calibrated to 88 on the published index scale (record calibration 2026-08-02).", "notes": [ { "code": "overall_calibration", "params": { "raw": 74, "calibrated": 88, "calibration": "2026-08-02" } } ], "value": 88, "inputs": { "security": 85, "vitality": 85, "community": 46, "governance": 68, "calibration": "2026-08-02", "engineering": 84, "ai_readiness": 74, "weighted_overall_raw": 74 }, "components": [] }, "categories": [ { "key": "vitality", "band": "excellent", "name": "Vitality", "value": 85, "weight": 0.21, "metrics": [ { "key": "development_activity", "band": "good", "name": "Development activity", "note": null, "notes": [], "value": 77, "inputs": { "commits_last_year": 452, "human_commit_share": 1, "days_since_last_push": 0, "active_weeks_last_year": 19 }, "components": [ { "key": "push_recency", "name": "Push recency", "detail": "last push 0 days ago", "points": 36, "status": "met", "details": [ { "code": "push_recency", "params": { "days": 0 } } ], "max_points": 36 }, { "key": "commit_cadence", "name": "Commit cadence", "detail": "19/52 weeks with commits", "points": 13.2, "status": "partial", "details": [ { "code": "commit_cadence_weeks", "params": { "weeks": 19 } } ], "max_points": 36 }, { "key": "commit_volume", "name": "Commit volume", "detail": "452 commits in the last year", "points": 18, "status": "met", "details": [ { "code": "commits_last_year", "params": { "count": 452 } } ], "max_points": 18 }, { "key": "openssf_scorecard_maintained", "name": "OpenSSF Scorecard: Maintained", "detail": "30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10", "points": 10, "status": "met", "details": [], "max_points": 10 } ] }, { "key": "release_discipline", "band": "exceptional", "name": "Release discipline", "note": null, "notes": [], "value": 98, "inputs": { "releases_count": 22, "latest_release_tag": "v0.12.0", "releases_from_tags": false, "days_since_latest_release": 12, "mean_days_between_releases": 6.3 }, "components": [ { "key": "ships_releases", "name": "Ships releases", "detail": "22 releases published", "points": 27, "status": "met", "details": [ { "code": "releases_published", "params": { "count": 22 } } ], "max_points": 27 }, { "key": "release_recency", "name": "Release recency", "detail": "latest release 12 days ago", "points": 36, "status": "met", "details": [ { "code": "release_recency", "params": { "days": 12 } } ], "max_points": 36 }, { "key": "release_cadence", "name": "Release cadence", "detail": "a release every ~6.3 days", "points": 27, "status": "met", "details": [ { "code": "release_cadence", "params": { "gap": 6.3 } } ], "max_points": 27 }, { "key": "openssf_scorecard_signed_releases", "name": "OpenSSF Scorecard: Signed-Releases", "detail": "5 out of the last 5 releases have a total of 5 signed artifacts.", "points": 8, "status": "partial", "details": [], "max_points": 10 } ] }, { "key": "abandonment", "band": "exceptional", "name": "Abandonment", "note": null, "notes": [], "value": 100, "inputs": { "cap": null, "state": "unverified", "guards": [], "signals": [], "red_flag": false, "multiplier_pct": 100, "declared_reason": null, "unverified_reason": "repository_too_young", "unanswered_open_prs": null, "unanswered_open_issues": null, "days_since_last_merged_pr": null, "days_since_last_human_commit": null, "days_since_last_human_commit_is_floor": false }, "components": [ { "key": "project_is_still_maintained", "name": "Project is still maintained", "detail": "maintenance record not established from the collected data", "points": 100, "status": "met", "details": [ { "code": "abandonment_unverified", "params": {} } ], "max_points": 100 } ] } ], "description": "Is the project alive — is code being written and are releases shipping?" }, { "key": "community", "band": "weak", "name": "Community & Adoption", "value": 46, "weight": 0.17, "metrics": [ { "key": "popularity", "band": "critical", "name": "Popularity & adoption", "note": null, "notes": [], "value": 5, "inputs": { "forks": 0, "stars": 3, "watchers": 1, "growth_state": "unverified", "growth_factor_pct": 100, "growth_unverified_reason": "no_history" }, "components": [ { "key": "stars", "name": "Stars", "detail": "3 stars", "points": 4.9, "status": "partial", "details": [ { "code": "stars", "params": { "count": 3 } } ], "max_points": 60 }, { "key": "forks", "name": "Forks", "detail": "0 forks", "points": 0, "status": "missed", "details": [ { "code": "forks", "params": { "count": 0 } } ], "max_points": 25 }, { "key": "watchers", "name": "Watchers", "detail": "1 watchers", "points": 0, "status": "missed", "details": [ { "code": "watchers", "params": { "count": 1 } } ], "max_points": 15 } ] }, { "key": "community_health", "band": "excellent", "name": "Community health", "note": null, "notes": [], "value": 92, "inputs": { "has_readme": true, "has_license": true, "readme_badges": 10, "has_contributing": true, "has_issue_template": false, "has_code_of_conduct": true, "readme_badge_services": [ "api.securityscorecards.dev", "codecov.io", "github.com", "shields.io", "www.bestpractices.dev" ], "has_pull_request_template": true }, "components": [ { "key": "readme", "name": "README", "detail": null, "points": 22.5, "status": "met", "details": [], "max_points": 22.5 }, { "key": "license", "name": "License", "detail": "recognized license (GPL-2.0)", "points": 22.5, "status": "met", "details": [ { "code": "license_standard", "params": {} }, { "code": "license_spdx", "params": { "spdx": "GPL-2.0" } } ], "max_points": 22.5 }, { "key": "contributing_guide", "name": "CONTRIBUTING guide", "detail": null, "points": 18, "status": "met", "details": [], "max_points": 18 }, { "key": "code_of_conduct", "name": "Code of conduct", "detail": null, "points": 13.5, "status": "met", "details": [], "max_points": 13.5 }, { "key": "issue_template", "name": "Issue template", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 7.2 }, { "key": "pr_template", "name": "PR template", "detail": null, "points": 6.3, "status": "met", "details": [], "max_points": 6.3 } ] }, { "key": "ecosystem_adoption", "band": "weak", "name": "Ecosystem adoption (downloads)", "note": null, "notes": [], "value": 46, "inputs": { "packages": [ "netresearch/nr-passkeys-be" ], "dependents": 1, "ecosystems": "packagist", "total_downloads": 5516, "monthly_downloads": 2161 }, "components": [ { "key": "monthly_downloads", "name": "Monthly downloads", "detail": "2,161 downloads/month across packagist", "points": 44.5, "status": "partial", "details": [ { "code": "downloads_monthly", "params": { "count": 2161, "ecosystems": "packagist" } } ], "max_points": 80 }, { "key": "registry_dependents", "name": "Registry dependents", "detail": "1 packages depend on it", "points": 2, "status": "partial", "details": [ { "code": "registry_dependents", "params": { "count": 1 } } ], "max_points": 20 } ] } ], "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?" }, { "key": "governance", "band": "good", "name": "Sustainability & Governance", "value": 68, "weight": 0.23, "metrics": [ { "key": "maintainer_resilience", "band": "at_risk", "name": "Maintainer resilience (bus factor)", "note": null, "notes": [], "value": 22, "inputs": { "bus_factor": 1, "contributors_sampled": 2, "top_contributor_share": 0.978 }, "components": [ { "key": "bus_factor", "name": "Bus factor", "detail": "1 contributor(s) cover half of all commits", "points": 9, "status": "partial", "details": [ { "code": "bus_factor", "params": { "count": 1 } } ], "max_points": 54 }, { "key": "commit_distribution", "name": "Commit distribution", "detail": "top contributor authored 98% of commits", "points": 0.5, "status": "partial", "details": [ { "code": "top_contributor_share", "params": { "share": 98 } } ], "max_points": 22.5 }, { "key": "contributor_breadth", "name": "Contributor breadth", "detail": "2 contributors", "points": 2.7, "status": "partial", "details": [ { "code": "contributors_sampled", "params": { "count": 2 } } ], "max_points": 13.5 }, { "key": "openssf_scorecard_contributors", "name": "OpenSSF Scorecard: Contributors", "detail": "project has 3 contributing companies or organizations -- score normalized to 10", "points": 10, "status": "met", "details": [], "max_points": 10 } ] }, { "key": "responsiveness", "band": "exceptional", "name": "Issue & PR responsiveness", "note": "Excluded from scoring (no data or not applicable): Newcomer PR acceptance. Remaining weights renormalized.", "notes": [ { "code": "excluded_no_data", "params": { "components": [ "newcomer_pr_acceptance" ] } }, { "code": "weights_renormalized", "params": {} } ], "value": 98, "inputs": { "merged_prs": 100, "open_issues": 0, "closed_issues": 4, "prs_merged_7d": 17, "prs_decided_7d": 17, "prs_merged_30d": 27, "prs_decided_30d": 28, "issue_closed_ratio": 1, "closed_unmerged_prs": 5, "first_time_authors_30d": 0, "first_time_prs_merged_30d": 0, "first_time_prs_decided_30d": 0 }, "components": [ { "key": "issue_resolution", "name": "Issue resolution", "detail": "100% of issues closed", "points": 42, "status": "met", "details": [ { "code": "issues_closed_share", "params": { "share": 100 } } ], "max_points": 42 }, { "key": "pr_acceptance", "name": "PR acceptance", "detail": "100/105 decided PRs merged", "points": 28.6, "status": "partial", "details": [ { "code": "decided_prs_merged", "params": { "merged": 100, "decided": 105 } } ], "max_points": 30 }, { "key": "newcomer_pr_acceptance", "name": "Newcomer PR acceptance", "detail": "no first-time contributor's PR decided in 30d", "points": 0, "status": "excluded", "details": [ { "code": "no_newcomer_prs", "params": { "days": 30 } } ], "max_points": 13 }, { "key": "openssf_scorecard_code_review", "name": "OpenSSF Scorecard: Code-Review", "detail": "all changesets reviewed", "points": 15, "status": "met", "details": [], "max_points": 15 } ] }, { "key": "stewardship", "band": "good", "name": "Ownership & stewardship", "note": null, "notes": [], "value": 66, "inputs": { "followers": 39, "owner_type": "Organization", "is_verified": null, "owner_login": "netresearch", "public_repos": 280, "account_age_days": 6112 }, "components": [ { "key": "ownership_backing", "name": "Ownership backing", "detail": "organization-owned", "points": 30, "status": "met", "details": [ { "code": "owner_organization", "params": {} } ], "max_points": 30 }, { "key": "verified_domain", "name": "Verified domain", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 20 }, { "key": "owner_reach", "name": "Owner reach", "detail": "39 followers of netresearch", "points": 11.5, "status": "partial", "details": [ { "code": "owner_followers", "params": { "count": 39, "login": "netresearch" } } ], "max_points": 25 }, { "key": "track_record", "name": "Track record", "detail": "280 public repos, account ~16 yr old", "points": 25, "status": "met", "details": [ { "code": "public_repos", "params": { "count": 280 } }, { "code": "account_age_years", "params": { "years": 16 } } ], "max_points": 25 } ] }, { "key": "package_maintenance", "band": "exceptional", "name": "Package maintenance", "note": null, "notes": [], "value": 100, "inputs": { "packages": [ "netresearch/nr-passkeys-be" ], "ecosystems": "packagist", "any_deprecated": false, "min_days_since_publish": 12 }, "components": [ { "key": "published_resolvable", "name": "Published & resolvable", "detail": "1 package(s) on packagist", "points": 25, "status": "met", "details": [ { "code": "packages_published", "params": { "count": 1, "ecosystems": "packagist" } } ], "max_points": 25 }, { "key": "publish_recency", "name": "Publish recency", "detail": "latest publish 12 days ago", "points": 35, "status": "met", "details": [ { "code": "publish_recency", "params": { "days": 12 } } ], "max_points": 35 }, { "key": "version_history", "name": "Version history", "detail": "23 published versions", "points": 20, "status": "met", "details": [ { "code": "published_versions", "params": { "count": 23 } } ], "max_points": 20 }, { "key": "not_deprecated", "name": "Not deprecated", "detail": "active, not deprecated or yanked", "points": 20, "status": "met", "details": [ { "code": "package_not_deprecated", "params": {} } ], "max_points": 20 } ] } ], "description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?" }, { "key": "engineering", "band": "excellent", "name": "Engineering Quality", "value": 84, "weight": 0.19, "metrics": [ { "key": "engineering_practices", "band": "excellent", "name": "Engineering practices", "note": null, "notes": [], "value": 90, "inputs": { "has_ci": true, "has_tests": true, "has_editorconfig": true, "has_linter_config": true, "has_precommit_config": false }, "components": [ { "key": "ci_workflows", "name": "CI workflows", "detail": "14 workflow(s)", "points": 24, "status": "met", "details": [ { "code": "ci_workflows", "params": { "count": 14 } } ], "max_points": 24 }, { "key": "tests_present", "name": "Tests present", "detail": null, "points": 24, "status": "met", "details": [], "max_points": 24 }, { "key": "linter_config", "name": "Linter config", "detail": ".php-cs-fixer.php, phpstan.neon", "points": 16, "status": "met", "details": [ { "code": "file_list", "params": { "files": ".php-cs-fixer.php, phpstan.neon" } } ], "max_points": 16 }, { "key": "pre_commit_hooks", "name": "Pre-commit hooks", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 9.6 }, { "key": "editorconfig", "name": ".editorconfig", "detail": null, "points": 6.4, "status": "met", "details": [], "max_points": 6.4 }, { "key": "openssf_scorecard_ci_tests", "name": "OpenSSF Scorecard: CI-Tests", "detail": "13 out of 13 merged PRs checked by a CI test -- score normalized to 10", "points": 20, "status": "met", "details": [], "max_points": 20 } ] }, { "key": "documentation", "band": "good", "name": "Documentation", "note": null, "notes": [], "value": 75, "inputs": { "topics": [ "authentication", "fido2", "passkeys", "passwordless", "php", "typo3", "typo3-extension", "webauthn" ], "has_wiki": false, "homepage": null, "has_readme": true, "has_docs_dir": true, "has_description": true }, "components": [ { "key": "readme", "name": "README", "detail": null, "points": 30, "status": "met", "details": [], "max_points": 30 }, { "key": "documentation_directory", "name": "Documentation directory", "detail": null, "points": 25, "status": "met", "details": [], "max_points": 25 }, { "key": "documentation_homepage_site", "name": "Documentation / homepage site", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 15 }, { "key": "repository_description", "name": "Repository description", "detail": null, "points": 10, "status": "met", "details": [], "max_points": 10 }, { "key": "topics", "name": "Topics", "detail": "8 topics", "points": 10, "status": "met", "details": [ { "code": "topics_count", "params": { "count": 8 } } ], "max_points": 10 }, { "key": "wiki", "name": "Wiki", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 10 } ] } ], "description": "Are baseline engineering and documentation practices in place?" }, { "key": "security", "band": "excellent", "name": "Security", "value": 85, "weight": 0.16, "metrics": [ { "key": "security_posture", "band": "excellent", "name": "Security posture", "note": "Excluded from scoring (no data or not applicable): Dangerous-Workflow, Packaging, Pinned-Dependencies, Token-Permissions. Remaining weights renormalized.", "notes": [ { "code": "excluded_no_data", "params": { "components": [ "dangerous_workflow", "packaging", "pinned_dependencies", "token_permissions" ] } }, { "code": "weights_renormalized", "params": {} } ], "value": 85, "inputs": { "source": "openssf_scorecard", "checks_evaluated": 14, "scorecard_version": "v5.5.0", "checks_inconclusive": 4, "scorecard_aggregate": 8.5 }, "components": [ { "key": "binary_artifacts", "name": "Binary-Artifacts", "detail": "no binaries found in the repo", "points": 7.5, "status": "met", "details": [], "max_points": 7.5 }, { "key": "branch_protection", "name": "Branch-Protection", "detail": "branch protection is not maximal on development and all release branches", "points": 3, "status": "partial", "details": [], "max_points": 7.5 }, { "key": "ci_tests", "name": "CI-Tests", "detail": "13 out of 13 merged PRs checked by a CI test -- score normalized to 10", "points": 2.5, "status": "met", "details": [], "max_points": 2.5 }, { "key": "cii_best_practices", "name": "CII-Best-Practices", "detail": "badge detected: Silver", "points": 1.8, "status": "partial", "details": [], "max_points": 2.5 }, { "key": "code_review", "name": "Code-Review", "detail": "all changesets reviewed", "points": 7.5, "status": "met", "details": [], "max_points": 7.5 }, { "key": "contributors", "name": "Contributors", "detail": "project has 3 contributing companies or organizations -- score normalized to 10", "points": 2.5, "status": "met", "details": [], "max_points": 2.5 }, { "key": "dangerous_workflow", "name": "Dangerous-Workflow", "detail": "no workflows found", "points": 0, "status": "excluded", "details": [ { "code": "no_data", "params": {} } ], "max_points": 10 }, { "key": "dependency_update_tool", "name": "Dependency-Update-Tool", "detail": "update tool detected", "points": 7.5, "status": "met", "details": [], "max_points": 7.5 }, { "key": "fuzzing", "name": "Fuzzing", "detail": "project is not fuzzed", "points": 0, "status": "missed", "details": [], "max_points": 5 }, { "key": "license", "name": "License", "detail": "license file detected", "points": 2.5, "status": "met", "details": [], "max_points": 2.5 }, { "key": "maintained", "name": "Maintained", "detail": "30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10", "points": 7.5, "status": "met", "details": [], "max_points": 7.5 }, { "key": "packaging", "name": "Packaging", "detail": "packaging workflow not detected", "points": 0, "status": "excluded", "details": [ { "code": "no_data", "params": {} } ], "max_points": 5 }, { "key": "pinned_dependencies", "name": "Pinned-Dependencies", "detail": "no dependencies found", "points": 0, "status": "excluded", "details": [ { "code": "no_data", "params": {} } ], "max_points": 5 }, { "key": "sast", "name": "SAST", "detail": "SAST tool is run on all commits", "points": 5, "status": "met", "details": [], "max_points": 5 }, { "key": "security_policy", "name": "Security-Policy", "detail": "security policy file detected", "points": 5, "status": "met", "details": [], "max_points": 5 }, { "key": "signed_releases", "name": "Signed-Releases", "detail": "5 out of the last 5 releases have a total of 5 signed artifacts.", "points": 6, "status": "partial", "details": [], "max_points": 7.5 }, { "key": "token_permissions", "name": "Token-Permissions", "detail": "No tokens found", "points": 0, "status": "excluded", "details": [ { "code": "no_data", "params": {} } ], "max_points": 7.5 }, { "key": "vulnerabilities", "name": "Vulnerabilities", "detail": "0 existing vulnerabilities detected", "points": 7.5, "status": "met", "details": [], "max_points": 7.5 } ] }, { "key": "high_risk_jurisdiction_exposure", "band": "exceptional", "name": "High-Risk Jurisdiction Exposure", "note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.", "notes": [ { "code": "jurisdiction_evidence_limits", "params": {} } ], "value": 100, "inputs": { "meaning": "self-published location evidence; not nationality or citizenship", "red_flag": false, "exposures": [], "policy_countries": [ "Russia", "Iran", "North Korea" ], "commit_weight_rule": { "min_commits": 50, "min_commit_share": 0.1 }, "review_only_matches": 0, "below_threshold_exposures": [], "assessed_self_published_locations": 4 }, "components": [ { "key": "policy_exposure_multiplier", "name": "Policy exposure multiplier", "detail": "no confirmed policy-scope location match", "points": 100, "status": "met", "details": [ { "code": "jurisdiction_no_match", "params": {} } ], "max_points": 100 } ] } ], "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?" }, { "key": "ai_readiness", "band": "good", "name": "AI Readiness", "value": 74, "weight": 0.04, "metrics": [ { "key": "ai_agent_context", "band": "excellent", "name": "Agent context & guidance", "note": null, "notes": [], "value": 85, "inputs": { "has_llms_txt": false, "legible_history_share": 0.99, "agent_instruction_files": [ ".ddev/AGENTS.md", ".github/copilot-instructions.md", ".github/workflows/AGENTS.md", ".github/workflows/CLAUDE.md", "AGENTS.md", "CLAUDE.md", "Classes/AGENTS.md", "Classes/CLAUDE.md", "Documentation/AGENTS.md", "Documentation/CLAUDE.md", "Resources/AGENTS.md", "Resources/CLAUDE.md", "Tests/AGENTS.md", "Tests/CLAUDE.md" ], "agent_instruction_max_bytes": 11003 }, "components": [ { "key": "agent_instructions", "name": "Agent instructions", "detail": ".ddev/AGENTS.md, .github/copilot-instructions.md, .github/workflows/AGENTS.md, .github/workflows/CLAUDE.md, AGENTS.md, CLAUDE.md, Classes/AGENTS.md, Classes/CLAUDE.md, Documentation/AGENTS.md, Documentation/CLAUDE.md, Resources/AGENTS.md, Resources/CLAUDE.md, Tests/AGENTS.md, Tests/CLAUDE.md", "points": 45, "status": "met", "details": [ { "code": "file_list", "params": { "files": ".ddev/AGENTS.md, .github/copilot-instructions.md, .github/workflows/AGENTS.md, .github/workflows/CLAUDE.md, AGENTS.md, CLAUDE.md, Classes/AGENTS.md, Classes/CLAUDE.md, Documentation/AGENTS.md, Documentation/CLAUDE.md, Resources/AGENTS.md, Resources/CLAUDE.md, Tests/AGENTS.md, Tests/CLAUDE.md" } } ], "max_points": 45 }, { "key": "machine_readable_docs_llms_txt", "name": "Machine-readable docs (llms.txt)", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 15 }, { "key": "legible_commit_history", "name": "Legible commit history", "detail": "99 of 100 human commits state their intent (structured subject or explanatory body)", "points": 40, "status": "met", "details": [ { "code": "legible_history", "params": { "legible": 99, "sampled": 100 } } ], "max_points": 40 } ] }, { "key": "ai_verify_loop", "band": "good", "name": "Verify loop (build / test / typecheck)", "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Pinned-Dependencies. Remaining weights renormalized.", "notes": [ { "code": "excluded_no_data", "params": { "components": [ "openssf_scorecard_pinned_dependencies" ] } }, { "code": "weights_renormalized", "params": {} } ], "value": 73, "inputs": { "has_nix": false, "has_tests": true, "lockfiles": [], "has_dockerfile": true, "typed_language": false, "bootstrap_files": [ "Makefile" ], "has_devcontainer": false, "has_linter_config": true, "typecheck_configs": [], "agent_commit_share": 0, "toolchain_manifests": [], "dependency_bot_commit_share": 0 }, "components": [ { "key": "one_command_bootstrap", "name": "One-command bootstrap", "detail": "Makefile", "points": 18, "status": "met", "details": [ { "code": "file_list", "params": { "files": "Makefile" } } ], "max_points": 18 }, { "key": "automated_tests", "name": "Automated tests", "detail": null, "points": 22, "status": "met", "details": [], "max_points": 22 }, { "key": "lint_format_config", "name": "Lint / format config", "detail": ".php-cs-fixer.php, phpstan.neon", "points": 11, "status": "met", "details": [ { "code": "file_list", "params": { "files": ".php-cs-fixer.php, phpstan.neon" } } ], "max_points": 11 }, { "key": "static_type_checking", "name": "Static type checking", "detail": null, "points": 0, "status": "missed", "details": [], "max_points": 11 }, { "key": "reproducible_environment", "name": "Reproducible environment", "detail": "Dockerfile", "points": 10, "status": "met", "details": [ { "code": "file_list", "params": { "files": "Dockerfile" } } ], "max_points": 10 }, { "key": "demonstrated_agent_practice", "name": "Demonstrated agent practice", "detail": "no agent-authored commits among the last 100", "points": 0, "status": "missed", "details": [ { "code": "no_agent_authored_commits", "params": { "sampled": 100 } } ], "max_points": 10 }, { "key": "automated_maintenance", "name": "Automated maintenance", "detail": "dependency automation configured, none observed in the sampled commits", "points": 5, "status": "partial", "details": [ { "code": "dependency_bot_config_only", "params": {} } ], "max_points": 8 }, { "key": "openssf_scorecard_pinned_dependencies", "name": "OpenSSF Scorecard: Pinned-Dependencies", "detail": "no dependencies found", "points": 0, "status": "excluded", "details": [ { "code": "no_data", "params": {} } ], "max_points": 10 } ] }, { "key": "ai_code_legibility", "band": "moderate", "name": "Code legibility for models", "note": null, "notes": [], "value": 55, "inputs": { "primary_language": "PHP", "largest_source_bytes": 66621, "source_files_sampled": 144, "oversized_source_files": 1 }, "components": [ { "key": "type_checkable_code", "name": "Type-checkable code", "detail": "PHP without a type-check config", "points": 0, "status": "missed", "details": [ { "code": "no_typecheck_config_language", "params": { "language": "PHP" } } ], "max_points": 45 }, { "key": "manageable_file_sizes", "name": "Manageable file sizes", "detail": "1/144 source files over 60KB", "points": 54.6, "status": "partial", "details": [ { "code": "oversized_source_files", "params": { "kb": 60, "sampled": 144, "oversized": 1 } } ], "max_points": 55 } ] } ], "description": "How well is the repo equipped to be developed and maintained with AI coding agents? Carries a deliberately small weight: agent tooling is a real maintenance signal, but its absence must never gate the top of the scale (calibration saturates at raw 91, so 100/100 remains reachable with AI Readiness at zero)." } ], "classification": { "top": [ "host-extension" ], "labels": [ "plugin" ], "scores": { "plugin": 10, "network-service": 3 }, "primary": "plugin", "evidence": [ { "tier": "declared", "label": "plugin", "source": "composer.type:typo3-cms-extension", "weight": 10 }, { "tier": "declared", "label": "library", "source": "npm.private", "weight": -6 }, { "tier": "distribution", "label": "library", "source": "registry:packagist", "weight": 6 }, { "tier": "structure", "label": "network-service", "source": "tree.compose", "weight": 3 } ], "artifacts": [ { "path": "composer.json", "labels": [ "plugin" ], "ecosystem": "packagist" }, { "path": "package.json", "labels": [], "ecosystem": "npm" } ], "confidence": "high", "host_extension": true, "runs_as_process": false, "consumed_by_code": false }, "metrics_version": "2.5.0" }, "warnings": [ "Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token", "No resolved dependencies carried a version and a supported ecosystem" ], "report_type": "repository", "generated_at": "2026-08-05T20:22:22.327074Z", "schema_version": "0.31.0", "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/n/netresearch/t3x-nr-passkeys-be.svg", "full_name": "netresearch/t3x-nr-passkeys-be", "license_state": "standard", "license_spdx": "GPL-2.0" }, "repoMeta": null, "notFound": false, "related": [ { "id": 34276, "full_name": "netresearch/t3x-nr-llm", "url": "https://github.com/netresearch/t3x-nr-llm", "description": "The shared AI foundation for TYPO3 — one LLM setup for every extension on your site", "ecosystem": "packagist", "ecosystems": [ "packagist", "npm" ], "primary_language": "PHP", "languages": [ "PHP" ], "topics": [ "ai", "claude", "llm", "openai", "php", "typo3", "typo3-extension", "anthropic", "gemini", "gpt", "embeddings", "ollama", "provider-abstraction", "streaming", "translation", "extension", "chatbot" ], "license_spdx": "GPL-2.0", "license_state": "standard", "stars": 4, "forks": 1, "watchers": 1, "monthly_downloads": 2421, "latest_score": 91, "latest_band": "excellent", "latest_scanned_at": "2026-07-22T06:09:38.371501Z", "has_high_risk_jurisdiction_exposure": false, "has_malicious_dependency": false, "growth_authenticity": "unverified", "abandonment_state": "maintained", "red_flags": [], "icon_url": "", "icon_source_type": null, "badge_url": "" }, { "id": 39573, "full_name": "authelia/authelia", "url": "https://github.com/authelia/authelia", "description": "The Single Sign-On Multi-Factor portal for web apps, now OpenID Certified™", "ecosystem": "go", "ecosystems": [ "go", "npm" ], "primary_language": "Go", "languages": [ "Go", "TypeScript" ], "topics": [ "totp", "ldap", "sso-authentication", "yubikey", "two-factor-authentication", "docker", "kubernetes", "sso", "multifactor", "push-notifications", "mfa", "two-factor", "authentication", "security", "golang", "2fa", "oauth2", "openid-connect", "webauthn", "passkeys" ], "license_spdx": "Apache-2.0", "license_state": "standard", "stars": 28461, "forks": 1449, "watchers": 164, "monthly_downloads": null, "latest_score": 99, "latest_band": "exceptional", "latest_scanned_at": "2026-08-05T02:21:11.280322Z", "has_high_risk_jurisdiction_exposure": false, "has_malicious_dependency": false, "growth_authenticity": "unverified", "abandonment_state": "maintained", "red_flags": [], "icon_url": "/icon/v1/authelia/authelia?v=7f419322-1", "icon_source_type": "homepage", "badge_url": "" }, { "id": 15781, "full_name": "TYPO3-Solr/ext-solr", "url": "https://github.com/TYPO3-Solr/ext-solr", "description": "A TYPO3 extension that integrates the Apache Solr search server with TYPO3 CMS. dkd Internet Service GmbH is developing the extension. Community contributions are welcome. See CONTRIBUTING.md for details.", "ecosystem": "packagist", "ecosystems": [ "packagist" ], "primary_language": "PHP", "languages": [ "PHP", "JavaScript" ], "topics": [ "solr", "typo3-cms", "php", "search", "cms", "cms-extension", "typo3", "typo3-cms-extension", "ext", "backend", "hacktoberfest" ], "license_spdx": "GPL-3.0", "license_state": "standard", "stars": 146, "forks": 269, "watchers": 15, "monthly_downloads": 61092, "latest_score": 89, "latest_band": "excellent", "latest_scanned_at": "2026-07-17T13:02:28.990518Z", "has_high_risk_jurisdiction_exposure": false, "has_malicious_dependency": false, "growth_authenticity": "unverified", "abandonment_state": "unverified", "red_flags": [], "icon_url": "", "icon_source_type": null, "badge_url": "" }, { "id": 16268, "full_name": "cakephp/authentication", "url": "https://github.com/cakephp/authentication", "description": "Authentication plugin for CakePHP. Can also be used in PSR7 based applications.", "ecosystem": "packagist", "ecosystems": [ "packagist", "npm" ], "primary_language": "PHP", "languages": [ "PHP" ], "topics": [ "php", "cakephp", "cakephp-plugin", "authentication", "middleware", "psr7-middleware", "auth" ], "license_spdx": "MIT", "license_state": "standard", "stars": 117, "forks": 104, "watchers": 29, "monthly_downloads": 198860, "latest_score": 93, "latest_band": "exceptional", "latest_scanned_at": "2026-07-17T14:15:36.317646Z", "has_high_risk_jurisdiction_exposure": false, "has_malicious_dependency": false, "growth_authenticity": "unverified", "abandonment_state": "unverified", "red_flags": [], "icon_url": "", "icon_source_type": null, "badge_url": "" }, { "id": 6448, "full_name": "netresearch/t3x-rte_ckeditor_image", "url": "https://github.com/netresearch/t3x-rte_ckeditor_image", "description": "Image support in CKEditor for the TYPO3 ecosystem - by Netresearch", "ecosystem": "packagist", "ecosystems": [ "packagist", "npm" ], "primary_language": "PHP", "languages": [ "PHP", "TypeScript", "Shell", "JavaScript" ], "topics": [ "ckeditor", "ckeditor-plugin", "magic-images", "rte-ckeditor", "typo3", "typo3-extension", "typo3cms-extension" ], "license_spdx": "AGPL-3.0", "license_state": "standard", "stars": 61, "forks": 67, "watchers": 8, "monthly_downloads": 20556, "latest_score": 92, "latest_band": "excellent", "latest_scanned_at": "2026-07-15T07:36:26.865862Z", "has_high_risk_jurisdiction_exposure": false, "has_malicious_dependency": false, "growth_authenticity": "unverified", "abandonment_state": "unverified", "red_flags": [], "icon_url": "", "icon_source_type": null, "badge_url": "" }, { "id": 19015, "full_name": "TYPO3/typo3", "url": "https://github.com/TYPO3/typo3", "description": "The TYPO3 Core - Enterprise Content Management System. Synchronized mirror of https://review.typo3.org/q/project:Packages/TYPO3.CMS", "ecosystem": "packagist", "ecosystems": [ "packagist", "npm" ], "primary_language": "PHP", "languages": [ "PHP" ], "topics": [ "cms", "php", "content-management", "enterprise", "typo3", "content-management-system", "extbase" ], "license_spdx": "GPL-2.0", "license_state": "standard", "stars": 1199, "forks": 707, "watchers": 111, "monthly_downloads": 1520, "latest_score": 91, "latest_band": "excellent", "latest_scanned_at": "2026-07-17T22:36:00.650649Z", "has_high_risk_jurisdiction_exposure": false, "has_malicious_dependency": false, "growth_authenticity": "unverified", "abandonment_state": "unverified", "red_flags": [], "icon_url": "", "icon_source_type": null, "badge_url": "" } ] } }
Öffentliches Register
Software-GesundheitsberichtSchema 0.31.0 · Metriken 2.5.0 · 2026-08-05 20:22 UTC

netresearch / t3x-nr-passkeys-be

TYPO3 extension for passwordless backend authentication via WebAuthn/FIDO2 Passkeys

PHPGPL-2.0★ 3 Sterne⑂ 0 Forksseit Feb. 2026Auf GitHub ansehen ↗

netresearch/t3x-nr-passkeys-be erreicht einen Gesundheitsindex von 88 von 100 und liegt damit im Bereich Exzellent. Am stärksten schneidet es bei Vitality (85/100) ab, am schwächsten bei Community & Adoption (46/100). Zuletzt heute aktualisiert. Ein einzelner Mitwirkender trägt den Großteil der jüngsten Arbeit.

88
gesamt / 100
Exzellent

Software-Gesundheitsindex

Metriken werden auf einer standardisierten Skala von 1–100 in gewichtete Kategorien gruppiert. Der Gesamtwert beginnt als ihr gewichtetes Mittel, kalibriert auf die Verteilung des öffentlichen Registers, sodass die Stufen Perzentilbedeutung tragen; sobald öffentliche Evidenz die Richtlinie für Hochrisikojurisdiktionen auslöst, wird die Bewertung angepasst und erhält die Obergrenze Gefährdet von 34.

88
Außergewöhnlich93-100Die Spitzengruppe des Registers (≈ obere 5 %); erfüllt im Wesentlichen alle geprüften Kriterien
Exzellent80-92Durchgehend stark; geringfügige Lücken
Gut65-79Gesund; Lücken sind begrenzt und beherrschbar
Mittel50-64Akzeptabel mit deutlichen Lücken; Überprüfung empfohlen
Schwach35-49Wesentliche Schwächen in mehreren Bereichen
Gefährdet20-34Erhebliche Schwächen; eine Übernahme erfordert Vorsicht
Kritisch1-19Schwerwiegende Probleme (aufgegeben, nur ein Maintainer, keine Hygiene)
VitalitätCommunity &VerbreitungNachhaltigkeit &GovernanceEngineering-QualitätSicherheitAI Readiness

Bewertungsprofil

Jede Achse ist eine Kategorie. Die Form zählt mehr als der Durchschnitt — ein gesundes Projekt füllt die gesamte Fläche, während ein Profil aus Spitzen und Kratern bedeutet, dass Stärke in einer Dimension Risiken in einer anderen verdeckt.

Der gewichtete Gesamtwert 74 wird auf der veröffentlichten Indexskala auf 88 kalibriert (Register-Kalibrierung 2026-08-02).

Eigentümerschaft

39 Follower280 öffentliche Reposseit Nov. 2009

Dieses Repository wird von einer Organisation getragen — geteilte, rechenschaftspflichtige Trägerschaft, die jeden einzelnen Maintainer überdauern kann.

Paket-Ökosysteme

RegistryPaketVersionDownloads / MonatVersionenZuletzt veröffentlichtTags
Packagistnetresearch/nr-passkeys-bev0.12.02.16123vor 12 Tagenauthenticationbackendtypo3passwordlessfido2webauthnpasskeys

Metriken nach Kategorie

Vitalität

Lebt das Projekt — wird Code geschrieben und werden Releases ausgeliefert?

85Exzellent · 21 % des Gesamtindex
Wie die Bewertung erfolgt
36/36Push-Aktualität — letzter Push vor 0 Tagen
13.2/36Commit-Rhythmus — 19/52 Wochen mit Commits
18/18Commit-Volumen — 452 Commits im letzten Jahr
10/10OpenSSF Scorecard: Maintained — 30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10
Verwendete Eingangsdaten
commits_last_year452
human_commit_share1
days_since_last_push0
active_weeks_last_year19

Release-Disziplin

98Außergewöhnlich
Wie die Bewertung erfolgt
27/27Liefert Releases aus — 22 Releases veröffentlicht
36/36Release-Aktualität — letztes Release vor 12 Tagen
27/27Release-Rhythmus — ein Release etwa alle 6,3 Tage
8/10OpenSSF Scorecard: Signed-Releases — 5 out of the last 5 releases have a total of 5 signed artifacts.
Verwendete Eingangsdaten
releases_count22
latest_release_tagv0.12.0
releases_from_tagsnein
days_since_latest_release12
mean_days_between_releases6,3

Community & Verbreitung

Hat das Projekt Nutzer, Downloads, Aufmerksamkeit und ein einladendes Umfeld für Beitragende?

46Schwach · 17 % des Gesamtindex
Wie die Bewertung erfolgt
4.9/60Stars — 3 Stars
0/25Forks — 0 Forks
0/15Watcher — 1 Watcher
Verwendete Eingangsdaten
forks0
stars3
watchers1
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
Wie die Bewertung erfolgt
22.5/22.5README
22.5/22.5Lizenz — anerkannte Lizenz (GPL-2.0)
18/18CONTRIBUTING-Leitfaden
13.5/13.5Verhaltenskodex
0/7.2Issue-Vorlage
6.3/6.3PR-Vorlage
Verwendete Eingangsdaten
has_readmeja
has_licenseja
readme_badges10
has_contributingja
has_issue_templatenein
has_code_of_conductja
readme_badge_servicesapi.securityscorecards.dev, codecov.io, github.com, shields.io, www.bestpractices.dev
has_pull_request_templateja
Wie die Bewertung erfolgt
44.5/80Downloads pro Monat — 2.161 Downloads/Monat über packagist
2/20Abhängige in der Registry — 1 Pakete hängen davon ab
Verwendete Eingangsdaten
packagesnetresearch/nr-passkeys-be
dependents1
ecosystemspackagist
total_downloads5.516
monthly_downloads2.161

Nachhaltigkeit & Governance

Überdauert das Projekt die Menschen, die es tragen — Bus-Faktor, Reaktionsfähigkeit, Trägerschaft und Paketpflege?

68Gut · 23 % des Gesamtindex
Wie die Bewertung erfolgt
9/54Bus-Faktor — 1 Beitragende decken die Hälfte aller Commits ab
0.5/22.5Commit-Verteilung — wichtigste beitragende Person verfasste 98 % der Commits
2.7/13.5Breite der Beitragenden — 2 Beitragende
10/10OpenSSF Scorecard: Contributors — project has 3 contributing companies or organizations -- score normalized to 10
Verwendete Eingangsdaten
bus_factor1
contributors_sampled2
top_contributor_share0,978
Wie die Bewertung erfolgt
42/42Issue-Lösungsquote — 100 % der Issues geschlossen
28.6/30PR-Annahme — 100/105 entschiedene PRs gemergt
0/13Newcomer PR acceptance — kein PR eines Erstbeitragenden in 30 Tagen entschieden
15/15OpenSSF Scorecard: Code-Review — all changesets reviewed
Verwendete Eingangsdaten
merged_prs100
open_issues0
closed_issues4
prs_merged_7d17
prs_decided_7d17
prs_merged_30d27
prs_decided_30d28
issue_closed_ratio1
closed_unmerged_prs5
first_time_authors_30d0
first_time_prs_merged_30d0
first_time_prs_decided_30d0
Von der Bewertung ausgeschlossen (keine Daten oder nicht anwendbar): newcomer_pr_acceptance. Die verbleibenden Gewichte wurden renormalisiert.
Wie die Bewertung erfolgt
30/30Organisatorische Trägerschaft — im Besitz einer Organisation
0/20Verifizierte Domain
11.5/25Reichweite des Inhabers — 39 Follower von netresearch
25/25Kontohistorie — 280 öffentliche Repos, Kontoalter ca. 16 Jahre
Verwendete Eingangsdaten
followers39
owner_typeOrganization
is_verified
owner_loginnetresearch
public_repos280
account_age_days6.112

Paketpflege

100Außergewöhnlich
Wie die Bewertung erfolgt
25/25Veröffentlicht & auflösbar — 1 Paket(e) auf packagist
35/35Veröffentlichungsaktualität — letzte Veröffentlichung vor 12 Tagen
20/20Versionshistorie — 23 veröffentlichte Versionen
20/20Nicht veraltet — aktiv, nicht veraltet oder zurückgezogen
Verwendete Eingangsdaten
packagesnetresearch/nr-passkeys-be
ecosystemspackagist
any_deprecatednein
min_days_since_publish12

Engineering-Qualität

Sind grundlegende Engineering- und Dokumentationspraktiken vorhanden?

84Exzellent · 19 % des Gesamtindex
Wie die Bewertung erfolgt
24/24CI-Workflows — 14 Workflow(s)
24/24Tests vorhanden
16/16Linter-Konfiguration — .php-cs-fixer.php, phpstan.neon
0/9.6Pre-Commit-Hooks
6.4/6.4.editorconfig
20/20OpenSSF Scorecard: CI-Tests — 13 out of 13 merged PRs checked by a CI test -- score normalized to 10
Verwendete Eingangsdaten
has_cija
has_testsja
has_editorconfigja
has_linter_configja
has_precommit_confignein
Wie die Bewertung erfolgt
30/30README
25/25Dokumentationsverzeichnis
0/15Dokumentations-/Homepage-Site
10/10Repository-Beschreibung
10/10Topics — 8 Topics
0/10Wiki
Verwendete Eingangsdaten
topicsauthentication, fido2, passkeys, passwordless, php, typo3, typo3-extension, webauthn
has_wikinein
homepage
has_readmeja
has_docs_dirja
has_descriptionja

Sicherheit

Sind die sichtbaren Sicherheits- und Lieferkettenpraktiken belastbar, ohne ungeklärte Exposition gegenüber Hochrisikojurisdiktionen?

85Exzellent · 16 % des Gesamtindex

Sicherheitslage

85Exzellent
Wie die Bewertung erfolgt
7.5/7.5Binary-Artifacts — no binaries found in the repo
3/7.5Branch-Protection — branch protection is not maximal on development and all release branches
2.5/2.5CI-Tests — 13 out of 13 merged PRs checked by a CI test -- score normalized to 10
1.8/2.5CII-Best-Practices — badge detected: Silver
7.5/7.5Code-Review — all changesets reviewed
2.5/2.5Contributors — project has 3 contributing companies or organizations -- score normalized to 10
0/10Dangerous-Workflow — keine Daten
7.5/7.5Dependency-Update-Tool — update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5Lizenz — license file detected
7.5/7.5Maintained — 30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10
0/5Packaging — keine Daten
0/5Pinned-Dependencies — keine Daten
5/5SAST — SAST tool is run on all commits
5/5Security-Policy — security policy file detected
6/7.5Signed-Releases — 5 out of the last 5 releases have a total of 5 signed artifacts.
0/7.5Token-Permissions — keine Daten
7.5/7.5Vulnerabilities — 0 existing vulnerabilities detected
Verwendete Eingangsdaten
sourceopenssf_scorecard
checks_evaluated14
scorecard_versionv5.5.0
checks_inconclusive4
scorecard_aggregate8,5
Von der Bewertung ausgeschlossen (keine Daten oder nicht anwendbar): dangerous_workflow, packaging, pinned_dependencies, token_permissions. Die verbleibenden Gewichte wurden renormalisiert.

AI Readiness

Wie gut ist das Repository dafür ausgestattet, mit KI-Coding-Agenten entwickelt und gepflegt zu werden? Trägt ein bewusst kleines Gewicht (4 %): Agenten-Tooling ist ein echtes Pflegesignal, doch ein Repository ohne jedes Signal kann weiterhin 100/100 erreichen.

74Gut · 4 % des Gesamtindex
Wie die Bewertung erfolgt
45/45Agentenanweisungen — .ddev/AGENTS.md, .github/copilot-instructions.md, .github/workflows/AGENTS.md, .github/workflows/CLAUDE.md, AGENTS.md, CLAUDE.md, Classes/AGENTS.md, Classes/CLAUDE.md, Documentation/AGENTS.md, Documentation/CLAUDE.md, Resources/AGENTS.md, Resources/CLAUDE.md, Tests/AGENTS.md, Tests/CLAUDE.md
0/15Maschinenlesbare Doku (llms.txt)
40/40Lesbare Commit-Historie — 99 von 100 menschlichen Commits benennen ihre Absicht (strukturierter Betreff oder erläuternder Text)
Verwendete Eingangsdaten
has_llms_txtnein
legible_history_share0,99
agent_instruction_files.ddev/AGENTS.md, .github/copilot-instructions.md, .github/workflows/AGENTS.md, .github/workflows/CLAUDE.md, AGENTS.md, CLAUDE.md, Classes/AGENTS.md, Classes/CLAUDE.md, Documentation/AGENTS.md, Documentation/CLAUDE.md, Resources/AGENTS.md, Resources/CLAUDE.md, Tests/AGENTS.md, Tests/CLAUDE.md
agent_instruction_max_bytes11.003
Wie die Bewertung erfolgt
18/18Bootstrap mit einem Befehl — Makefile
22/22Automatisierte Tests
11/11Lint-/Format-Konfiguration — .php-cs-fixer.php, phpstan.neon
0/11Statische Typprüfung
10/10Reproduzierbare Umgebung — Dockerfile
0/10Belegte Agentenpraxis — keine von Agenten verfassten Commits unter den letzten 100
5/8Automatisierte Wartung — Abhängigkeits-Automatisierung konfiguriert, in den erfassten Commits nicht beobachtet
0/10OpenSSF Scorecard: Pinned-Dependencies — keine Daten
Verwendete Eingangsdaten
has_nixnein
has_testsja
lockfiles
has_dockerfileja
typed_languagenein
bootstrap_filesMakefile
has_devcontainernein
has_linter_configja
typecheck_configs
agent_commit_share0
toolchain_manifests
dependency_bot_commit_share0
Von der Bewertung ausgeschlossen (keine Daten oder nicht anwendbar): OpenSSF Scorecard: Pinned-Dependencies. Die verbleibenden Gewichte wurden renormalisiert.
Wie die Bewertung erfolgt
0/45Typprüfbarer Code — PHP ohne Typprüfungs-Konfiguration
54.6/55Handhabbare Dateigrößen — 1/144 Quelldateien über 60 KB
Verwendete Eingangsdaten
primary_languagePHP
largest_source_bytes66.621
source_files_sampled144
oversized_source_files1

Eckdaten

3GitHub-Sterne
2Mitwirkende
452Commits, letzte 12 Monate
0Tage seit letztem Push
22Releases
1Bus-Faktor
0offene Issues
npm, PackagistPaket-Ökosysteme

Warnungen zur Datenerhebung

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token
  • No resolved dependencies carried a version and a supported ecosystem

Weitere Details

OpenSSF Scorecard 8.5 / 10
8.5Gesamtwert

Unabhängige, werkzeugneutrale Sicherheitsbewertung durch das quelloffene OpenSSF Scorecard. Jede Prüfung honoriert eine Sicherheits-Praxis, nicht das Werkzeug eines bestimmten Anbieters. Prüfungen, die Scorecard nicht ermitteln konnte, sind mit k. A. markiert und vom Sicherheitswert ausgeschlossen (nie als null gezählt).Scorecard v5.5.0 · 2026-08-05 20:22 UTC

10Binary-Artifactsno binaries found in the repo
4Branch-Protectionbranch protection is not maximal on development and all release branches
10CI-Tests13 out of 13 merged PRs checked by a CI test -- score normalized to 10
7CII-Best-Practicesbadge detected: Silver
10Code-Reviewall changesets reviewed
10Contributorsproject has 3 contributing companies or organizations -- score normalized to 10
k. A.Dangerous-Workflowno workflows found
10Dependency-Update-Toolupdate tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
10Maintained30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10
k. A.Packagingpackaging workflow not detected
k. A.Pinned-Dependenciesno dependencies found
10SASTSAST tool is run on all commits
10Security-Policysecurity policy file detected
8Signed-Releases5 out of the last 5 releases have a total of 5 signed artifacts.
k. A.Token-PermissionsNo tokens found
10Vulnerabilities0 existing vulnerabilities detected
Direkte Abhängigkeiten 4
RegistryPaketVersionsvorgabeManifest
Packagisttypo3/cms-core^12.4 || ^13.4 || ^14.3composer.json
Packagisttypo3/cms-backend^12.4 || ^13.4 || ^14.3composer.json
Packagisttypo3/cms-setup^12.4 || ^13.4 || ^14.3composer.json
Packagistweb-auth/webauthn-lib^5.3composer.json
Alle Abhängigkeiten 22

Vollständig aufgelöster Abhängigkeitssatz aus dem GitHub-Abhängigkeitsgraphen: 4 direkte und 18 indirekte (transitive) Pakete. Die transitive Hülle ist vollständig, wenn das Repository eine Lockfile eincheckt.

RegistryPaketVersionBeziehung
Packagisttypo3/cms-backenddirekt
Packagisttypo3/cms-coredirekt
Packagisttypo3/cms-setupdirekt
Packagistweb-auth/webauthn-libdirekt
npm@playwright/test^1.50indirekt
npm@vitest/coverage-v8^4.0indirekt
npmjsdom^29.0indirekt
npmvitest^4.0indirekt
Packagistdg/bypass-finalsindirekt
Packagistergebnis/phpstan-rulesindirekt
Packagistfriendsofphp/php-cs-fixerindirekt
Packagistnetresearch/typo3-ci-workflowsindirekt
Packagistphpindirekt
Packagistphpat/phpatindirekt
Packagistphpstan/extension-installerindirekt
Packagistphpstan/phpstanindirekt
Packagistphpstan/phpstan-deprecation-rulesindirekt
Packagistphpstan/phpstan-phpunitindirekt
Packagistphpstan/phpstan-strict-rulesindirekt
Packagistphpunit/phpunitindirekt
Packagisttypo3/cms-dashboardindirekt
Packagisttypo3/testing-frameworkindirekt
Abhängigkeits-Advisories nicht bewertet

Der Advisory-Abgleich konnte für diesen Bericht nicht ausgeführt werden: No resolved dependencies carried a version and a supported ecosystem

JSON-Rohbericht maschinenlesbar

Bewertungen sind Signale, keine Garantien. Sie spiegeln öffentlich sichtbare Praxis auf GitHub wider — kein Code-Audit und keine Sicherheitsgarantie.

Fehlende Daten werden ausgeschlossen und die Gewichte neu normiert, nie als null bewertet. Die Methodik ist versioniert und offen: Metriken v2.5.0, Schema v0.31.0 — vollständige Methodik · Metriken-Wiki.

Wie ein einzelnes Ergebnis im Gesamtregister steht: aggregierte StatistikenPackagist.