, 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": "" } ] } }
Registro público
Informe de salud del softwareesquema 0.31.0 · métricas 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 estrellas⑂ 0 forksdesde feb 2026Ver en GitHub ↗
TipoComplementocómo se determina

netresearch/t3x-nr-passkeys-be tiene un índice de salud de 88 sobre 100, lo que lo sitúa en la banda Excelente. Su puntuación más alta es Vitality (85/100) y la más baja, Community & Adoption (46/100). Se actualizó por última vez hoy. Una sola persona concentra la mayor parte del trabajo reciente.

88
global / 100
Excelente

Índice de salud del software

Las métricas se agrupan en categorías ponderadas sobre una escala estandarizada de 1 a 100. El resultado global parte de su media ponderada, calibrada contra la distribución del registro público para que las bandas tengan significado percentil; cuando la evidencia pública activa la Política de Jurisdicciones de Alto Riesgo, la calificación se ajusta y recibe un límite «En riesgo» de 34.

88
Excepcional93-100El nivel más alto del registro (≈ el 5% superior); cumple prácticamente todos los criterios evaluados
Excelente80-92Sólido en todos los frentes; carencias menores
Bueno65-79Saludable; carencias limitadas y manejables
Moderado50-64Aceptable con carencias notables; se recomienda revisión
Débil35-49Debilidades sustanciales en varias áreas
En riesgo20-34Debilidades significativas; su adopción exige cautela
Crítico1-19Problemas graves (proyecto abandonado, un solo mantenedor, sin higiene)
VitalidadComunidad yAdopciónSostenibilidady GobernanzaCalidad deIngenieríaSeguridadPreparaciónpara IA

Perfil de puntuación

Cada eje es una categoría. La forma importa más que la media: un proyecto sano llena toda la figura, mientras que un perfil de picos y cráteres indica que la fortaleza en una dimensión enmascara el riesgo en otra.

El resultado global ponderado 74 se calibra a 88 en la escala publicada del índice (calibración del registro 2026-08-02).

Titularidad

39 seguidores280 repositorios públicosdesde nov 2009

Este repositorio está respaldado por una organización: una custodia compartida y responsable que puede sobrevivir a cualquier mantenedor individual.

Ecosistemas de paquetes

RegistroPaqueteVersiónDescargas / mesVersionesÚltima publicaciónEtiquetas
Packagistnetresearch/nr-passkeys-bev0.12.0216123hace 12 díasauthenticationbackendtypo3passwordlessfido2webauthnpasskeys

Métricas por categoría

Vitalidad

¿Está vivo el proyecto: se escribe código y se publican versiones?

85Excelente · 21% del índice global
Cómo se puntúa
36/36Recencia de push — último push hace 0 días
13.2/36Cadencia de commits — 19/52 semanas con commits
18/18Volumen de commits — 452 commits en el último año
10/10OpenSSF Scorecard: Maintained — 30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10
Datos de entrada utilizados
commits_last_year452
human_commit_share1
days_since_last_push0
active_weeks_last_year19
Cómo se puntúa
27/27Publica versiones — 22 versiones publicadas
36/36Recencia de las versiones — última versión hace 12 días
27/27Cadencia de publicación — una versión cada ~6,3 días
8/10OpenSSF Scorecard: Signed-Releases — 5 out of the last 5 releases have a total of 5 signed artifacts.
Datos de entrada utilizados
releases_count22
latest_release_tagv0.12.0
releases_from_tagsno
days_since_latest_release12
mean_days_between_releases6,3

Comunidad y Adopción

¿Tiene el proyecto usuarios, descargas, atención y unas condiciones acogedoras para quienes contribuyen?

46Débil · 17% del índice global
Cómo se puntúa
4.9/60Estrellas — 3 estrellas
0/25Forks — 0 forks
0/15Observadores — 1 observadores
Datos de entrada utilizados
forks0
stars3
watchers1
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
Cómo se puntúa
22.5/22.5README
22.5/22.5Licencia — licencia reconocida (GPL-2.0)
18/18Guía CONTRIBUTING
13.5/13.5Código de conducta
0/7.2Plantilla de issues
6.3/6.3Plantilla de PR
Datos de entrada utilizados
has_readme
has_license
readme_badges10
has_contributing
has_issue_templateno
has_code_of_conduct
readme_badge_servicesapi.securityscorecards.dev, codecov.io, github.com, shields.io, www.bestpractices.dev
has_pull_request_template
Cómo se puntúa
44.5/80Descargas mensuales — 2161 descargas/mes en packagist
2/20Dependientes en el registro — 1 paquetes dependen de él
Datos de entrada utilizados
packagesnetresearch/nr-passkeys-be
dependents1
ecosystemspackagist
total_downloads5516
monthly_downloads2161

Sostenibilidad y Gobernanza

¿Sobrevivirá el proyecto a sus personas: factor bus, capacidad de respuesta, quién lo respalda y mantenimiento del paquete?

68Bueno · 23% del índice global
Cómo se puntúa
9/54Factor bus — la mitad de los commits recae en 1 contribuyente(s)
0.5/22.5Distribución de commits — el principal contribuyente firma el 98% de los commits
2.7/13.5Amplitud de contribuyentes — 2 contribuyentes
10/10OpenSSF Scorecard: Contributors — project has 3 contributing companies or organizations -- score normalized to 10
Datos de entrada utilizados
bus_factor1
contributors_sampled2
top_contributor_share0,978
Cómo se puntúa
42/42Resolución de issues — 100% de issues cerradas
28.6/30Aceptación de PR — 100/105 PR decididos fusionados
0/13Newcomer PR acceptance — ningún PR de un contribuyente primerizo decidido en 30 d
15/15OpenSSF Scorecard: Code-Review — all changesets reviewed
Datos de entrada utilizados
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
Excluidos de la puntuación (sin datos o no aplicable): newcomer_pr_acceptance. Los pesos restantes se han renormalizado.
Cómo se puntúa
30/30Respaldo de la propiedad — propiedad de una organización
0/20Dominio verificado
11.5/25Alcance del propietario — 39 seguidores de netresearch
25/25Trayectoria — 280 repos públicos, cuenta de ~16 años
Datos de entrada utilizados
followers39
owner_typeOrganization
is_verified
owner_loginnetresearch
public_repos280
account_age_days6112
Cómo se puntúa
25/25Publicado y resoluble — 1 paquete(s) en packagist
35/35Recencia de publicación — última publicación hace 12 días
20/20Historial de versiones — 23 versiones en el registro
20/20No obsoleto — activo, ni obsoleto ni retirado
Datos de entrada utilizados
packagesnetresearch/nr-passkeys-be
ecosystemspackagist
any_deprecatedno
min_days_since_publish12

Calidad de Ingeniería

¿Existen unas prácticas mínimas de ingeniería y documentación?

84Excelente · 19% del índice global
Cómo se puntúa
24/24Flujos de trabajo de CI — 14 flujo(s) de trabajo
24/24Pruebas presentes
16/16Configuración de linter — .php-cs-fixer.php, phpstan.neon
0/9.6Hooks de pre-commit
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
Datos de entrada utilizados
has_ci
has_tests
has_editorconfig
has_linter_config
has_precommit_configno
Cómo se puntúa
30/30README
25/25Directorio de documentación
0/15Sitio de documentación / página del proyecto
10/10Descripción del repositorio
10/10Topics — 8 topics
0/10Wiki
Datos de entrada utilizados
topicsauthentication, fido2, passkeys, passwordless, php, typo3, typo3-extension, webauthn
has_wikino
homepage
has_readme
has_docs_dir
has_description

Seguridad

¿Son sólidas las prácticas visibles de seguridad y de cadena de suministro, sin exposición jurisdiccional de alto riesgo sin resolver?

85Excelente · 16% del índice global
Cómo se puntúa
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 — sin datos
7.5/7.5Dependency-Update-Tool — update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5Licencia — 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 — sin datos
0/5Pinned-Dependencies — sin datos
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 — sin datos
7.5/7.5Vulnerabilities — 0 existing vulnerabilities detected
Datos de entrada utilizados
sourceopenssf_scorecard
checks_evaluated14
scorecard_versionv5.5.0
checks_inconclusive4
scorecard_aggregate8,5
Excluidos de la puntuación (sin datos o no aplicable): dangerous_workflow, packaging, pinned_dependencies, token_permissions. Los pesos restantes se han renormalizado.

Preparación para IA

¿Hasta qué punto está el repositorio preparado para desarrollarse y mantenerse con agentes de codificación de IA? Tiene un peso deliberadamente pequeño (4%): las herramientas para agentes son una señal real de mantenimiento, pero un repositorio sin ninguna puede alcanzar igualmente 100/100.

74Bueno · 4% del índice global
Cómo se puntúa
45/45Instrucciones para agentes — .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/15Documentación legible por máquinas (llms.txt)
40/40Historial de commits legible — 99 de 100 commits humanos declaran su intención (asunto estructurado o cuerpo explicativo)
Datos de entrada utilizados
has_llms_txtno
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
Cómo se puntúa
18/18Arranque con un solo comando — Makefile
22/22Pruebas automatizadas
11/11Configuración de lint / formato — .php-cs-fixer.php, phpstan.neon
0/11Verificación estática de tipos
10/10Entorno reproducible — Dockerfile
0/10Práctica demostrada con agentes — ningún commit con autoría de agente entre los últimos 100
5/8Mantenimiento automatizado — automatización de dependencias configurada, no observada en los commits muestreados
0/10OpenSSF Scorecard: Pinned-Dependencies — sin datos
Datos de entrada utilizados
has_nixno
has_tests
lockfiles
has_dockerfile
typed_languageno
bootstrap_filesMakefile
has_devcontainerno
has_linter_config
typecheck_configs
agent_commit_share0
toolchain_manifests
dependency_bot_commit_share0
Excluidos de la puntuación (sin datos o no aplicable): OpenSSF Scorecard: Pinned-Dependencies. Los pesos restantes se han renormalizado.
Cómo se puntúa
0/45Código verificable por tipos — PHP sin configuración de verificación de tipos
54.6/55Tamaños de archivo manejables — 1/144 archivos fuente de más de 60 KB
Datos de entrada utilizados
primary_languagePHP
largest_source_bytes66.621
source_files_sampled144
oversized_source_files1

Datos clave

3estrellas de GitHub
2contribuidores
452commits en los últimos 12 meses
0días desde el último push
22versiones publicadas
1factor bus
0issues abiertas
npm, Packagistecosistemas de paquetes

Advertencias de recopilación de datos

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

Más detalle

OpenSSF Scorecard 8.5 / 10
8.5agregado

Evaluación de seguridad independiente y agnóstica en cuanto a herramientas, procedente del proyecto de código abierto OpenSSF Scorecard. Cada comprobación premia una práctica de seguridad, no la herramienta de un proveedor concreto. Las comprobaciones que Scorecard no pudo determinar se marcan como n/d y se excluyen de la puntuación de seguridad (nunca se cuentan como cero).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
n/dDangerous-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
n/dPackagingpackaging workflow not detected
n/dPinned-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.
n/dToken-PermissionsNo tokens found
10Vulnerabilities0 existing vulnerabilities detected
Dependencias directas 4
RegistroPaqueteRestricción de versiónManifiesto
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
Todas las dependencias 22

Conjunto completo de dependencias resueltas según el grafo de dependencias de GitHub: 4 paquetes directos y 18 indirectos (transitivos). El cierre transitivo es completo cuando el repositorio incluye un lockfile.

RegistroPaqueteVersiónRelación
Packagisttypo3/cms-backenddirecta
Packagisttypo3/cms-coredirecta
Packagisttypo3/cms-setupdirecta
Packagistweb-auth/webauthn-libdirecta
npm@playwright/test^1.50indirecta
npm@vitest/coverage-v8^4.0indirecta
npmjsdom^29.0indirecta
npmvitest^4.0indirecta
Packagistdg/bypass-finalsindirecta
Packagistergebnis/phpstan-rulesindirecta
Packagistfriendsofphp/php-cs-fixerindirecta
Packagistnetresearch/typo3-ci-workflowsindirecta
Packagistphpindirecta
Packagistphpat/phpatindirecta
Packagistphpstan/extension-installerindirecta
Packagistphpstan/phpstanindirecta
Packagistphpstan/phpstan-deprecation-rulesindirecta
Packagistphpstan/phpstan-phpunitindirecta
Packagistphpstan/phpstan-strict-rulesindirecta
Packagistphpunit/phpunitindirecta
Packagisttypo3/cms-dashboardindirecta
Packagisttypo3/testing-frameworkindirecta
Avisos de dependencias sin evaluar

El cotejo de avisos no pudo ejecutarse para este informe: No resolved dependencies carried a version and a supported ecosystem

Informe JSON sin procesar legible por máquina

Las puntuaciones son señales, no garantías. Reflejan prácticas públicamente visibles en GitHub; no son una auditoría de código ni una garantía de seguridad.

Los datos ausentes se excluyen y los pesos se renormalizan; nunca se puntúan como cero. La metodología es versionada y abierta: métricas v2.5.0, esquema v0.31.0 — metodología completa · wiki de métricas.

Cómo se sitúa un resultado dentro del registro general: estadísticas agregadasPackagist.