, 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": ""
}
]
}
}
Public recordSoftware health report schema 0.31.0 · metrics 2.5.0 · 2026-08-05 20:22 UTC
TYPO3 extension for passwordless backend authentication via WebAuthn/FIDO2 Passkeys
Add score badge to README Compare to…
PHP GPL-2.0 ★ 3 stars ⑂ 0 forks since Feb 2026 View on GitHub ↗ netresearch/t3x-nr-passkeys-be holds a health index of 88 out of 100, placing it in the Excellent band. It scores highest on Vitality (85/100) and lowest on Community & Adoption (46/100). It was last updated today. A single contributor accounts for most of its recent work.
Software health index Metrics are grouped into weighted categories on one standardized 1–100 scale. Overall starts as their weighted mean, calibrated against the distribution of the public record so bands carry percentile meaning; when public evidence triggers the High-Risk Jurisdiction Policy, the rating is adjusted and receives an At Risk ceiling of 34.
88
Exceptional 93-100 The record's top tier (≈ top 5%); essentially all checked criteria met
Excellent 80-92 Strong across the board; minor gaps
Good 65-79 Healthy; gaps are limited and manageable
Moderate 50-64 Acceptable with notable gaps; review recommended
Weak 35-49 Material weaknesses across several areas
At Risk 20-34 Significant weaknesses; adoption warrants caution
Critical 1-19 Severe problems (abandoned, single-maintainer, no hygiene)
Vitality Community & Adoption Sustainability & Governance Engineering Quality Security AI Readiness Score profile Each axis is a category. The shape matters more than the average — a healthy subject fills the whole shape, while a spike-and-crater profile means strength in one dimension is masking risk in another.
The weighted overall 74 is calibrated to 88 on the published index scale (record calibration 2026-08-02).
Ownership 39 followers 280 public repos since Nov 2009
This repository is backed by an organization — shared, accountable stewardship that can outlive any single maintainer.
Package ecosystems
Metrics by category Is the project alive — is code being written and are releases shipping?
85 Excellent · 21% of overall
How it's scored 36/36 Push recency — last push 0 days ago 13.2/36 Commit cadence — 19/52 weeks with commits 18/18 Commit volume — 452 commits in the last year 10/10 OpenSSF Scorecard: Maintained — 30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10
Inputs used
How it's scored 27/27 Ships releases — 22 releases published 36/36 Release recency — latest release 12 days ago 27/27 Release cadence — a release every ~6.3 days 8/10 OpenSSF Scorecard: Signed-Releases — 5 out of the last 5 releases have a total of 5 signed artifacts.
Inputs used Does the project have users, downloads, attention, and a welcoming setup for contributors?
46 Weak · 17% of overall
How it's scored 4.9/60 Stars — 3 stars 0/25 Forks — 0 forks 0/15 Watchers — 1 watchers
Inputs used
How it's scored 22.5/22.5 README 22.5/22.5 License — recognized license (GPL-2.0) 18/18 CONTRIBUTING guide 13.5/13.5 Code of conduct 0/7.2 Issue template 6.3/6.3 PR template
Inputs used
How it's scored 44.5/80 Monthly downloads — 2,161 downloads/month across packagist 2/20 Registry dependents — 1 packages depend on it
Inputs used Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?
68 Good · 23% of overall
How it's scored 9/54 Bus factor — 1 contributor(s) cover half of all commits 0.5/22.5 Commit distribution — top contributor authored 98% of commits 2.7/13.5 Contributor breadth — 2 contributors 10/10 OpenSSF Scorecard: Contributors — project has 3 contributing companies or organizations -- score normalized to 10
Inputs used
How it's scored 42/42 Issue resolution — 100% of issues closed 28.6/30 PR acceptance — 100/105 decided PRs merged 0/13 Newcomer PR acceptance — no first-time contributor's PR decided in 30d 15/15 OpenSSF Scorecard: Code-Review — all changesets reviewed
Inputs used Excluded from scoring (no data or not applicable): newcomer_pr_acceptance. Remaining weights renormalized.
How it's scored 30/30 Ownership backing — organization-owned 0/20 Verified domain 11.5/25 Owner reach — 39 followers of netresearch 25/25 Track record — 280 public repos, account ~16 yr old
Inputs used
How it's scored 25/25 Published & resolvable — 1 package(s) on packagist 35/35 Publish recency — latest publish 12 days ago 20/20 Version history — 23 published versions 20/20 Not deprecated — active, not deprecated or yanked
Inputs used Are baseline engineering and documentation practices in place?
84 Excellent · 19% of overall
How it's scored 24/24 CI workflows — 14 workflow(s) 24/24 Tests present 16/16 Linter config — .php-cs-fixer.php, phpstan.neon 0/9.6 Pre-commit hooks 6.4/6.4 .editorconfig 20/20 OpenSSF Scorecard: CI-Tests — 13 out of 13 merged PRs checked by a CI test -- score normalized to 10
Inputs used
How it's scored 30/30 README 25/25 Documentation directory 0/15 Documentation / homepage site 10/10 Repository description 10/10 Topics — 8 topics 0/10 Wiki
Inputs used Are visible security and supply-chain practices strong, without unresolved high-risk jurisdiction exposure?
85 Excellent · 16% of overall
How it's scored 7.5/7.5 Binary-Artifacts — no binaries found in the repo 3/7.5 Branch-Protection — branch protection is not maximal on development and all release branches 2.5/2.5 CI-Tests — 13 out of 13 merged PRs checked by a CI test -- score normalized to 10 1.8/2.5 CII-Best-Practices — badge detected: Silver 7.5/7.5 Code-Review — all changesets reviewed 2.5/2.5 Contributors — project has 3 contributing companies or organizations -- score normalized to 10 0/10 Dangerous-Workflow — no data 7.5/7.5 Dependency-Update-Tool — update tool detected 0/5 Fuzzing — project is not fuzzed 2.5/2.5 License — license file detected 7.5/7.5 Maintained — 30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10 0/5 Packaging — no data 0/5 Pinned-Dependencies — no data 5/5 SAST — SAST tool is run on all commits 5/5 Security-Policy — security policy file detected 6/7.5 Signed-Releases — 5 out of the last 5 releases have a total of 5 signed artifacts. 0/7.5 Token-Permissions — no data 7.5/7.5 Vulnerabilities — 0 existing vulnerabilities detected
Inputs used Excluded from scoring (no data or not applicable): dangerous_workflow, packaging, pinned_dependencies, token_permissions. Remaining weights renormalized.
How well is the repo equipped to be developed and maintained with AI coding agents? Carries a deliberately small weight (4%): agent tooling is a real maintenance signal, but a repository with none can still reach 100/100.
74 Good · 4% of overall
How it's scored 45/45 Agent instructions — .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/15 Machine-readable docs (llms.txt) 40/40 Legible commit history — 99 of 100 human commits state their intent (structured subject or explanatory body)
Inputs used
How it's scored 18/18 One-command bootstrap — Makefile 22/22 Automated tests 11/11 Lint / format config — .php-cs-fixer.php, phpstan.neon 0/11 Static type checking 10/10 Reproducible environment — Dockerfile 0/10 Demonstrated agent practice — no agent-authored commits among the last 100 5/8 Automated maintenance — dependency automation configured, none observed in the sampled commits 0/10 OpenSSF Scorecard: Pinned-Dependencies — no data
Inputs used Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Pinned-Dependencies. Remaining weights renormalized.
How it's scored 0/45 Type-checkable code — PHP without a type-check config 54.6/55 Manageable file sizes — 1/144 source files over 60KB
Inputs used
Key facts 3 GitHub stars
2 contributors
452 commits, last 12 months
0 days since last push
22 releases
1 bus factor
0 open issues
npm, Packagist package ecosystems
Data collection warnings Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token No resolved dependencies carried a version and a supported ecosystem
More detail OpenSSF Scorecard 8.5 / 10 8.5 aggregate
Independent, tool-agnostic security assessment from the open-source OpenSSF Scorecard . Each check rewards a security practice , not a specific vendor's tool. Checks Scorecard could not determine are marked n/a and excluded from the security score (never counted as zero). Scorecard v5.5.0 · 2026-08-05 20:22 UTC
Direct dependencies 4 Registry Package Version constraint Manifest Packagist typo3/cms-core ^12.4 || ^13.4 || ^14.3composer.json Packagist typo3/cms-backend ^12.4 || ^13.4 || ^14.3composer.json Packagist typo3/cms-setup ^12.4 || ^13.4 || ^14.3composer.json Packagist web-auth/webauthn-lib ^5.3composer.json
All dependencies 22 Full resolved dependency set from the GitHub dependency graph: 4 direct and 18 indirect (transitive) packages. The transitive closure is complete when the repository commits a lockfile.
Registry Package Version Relation Packagist typo3/cms-backend —direct Packagist typo3/cms-core —direct Packagist typo3/cms-setup —direct Packagist web-auth/webauthn-lib —direct npm @playwright/test ^1.50indirect npm @vitest/coverage-v8 ^4.0indirect npm jsdom ^29.0indirect npm vitest ^4.0indirect Packagist dg/bypass-finals —indirect Packagist ergebnis/phpstan-rules —indirect Packagist friendsofphp/php-cs-fixer —indirect Packagist netresearch/typo3-ci-workflows —indirect Packagist php —indirect Packagist phpat/phpat —indirect Packagist phpstan/extension-installer —indirect Packagist phpstan/phpstan —indirect Packagist phpstan/phpstan-deprecation-rules —indirect Packagist phpstan/phpstan-phpunit —indirect Packagist phpstan/phpstan-strict-rules —indirect Packagist phpunit/phpunit —indirect Packagist typo3/cms-dashboard —indirect Packagist typo3/testing-framework —indirect
Dependency advisories not assessed Advisory matching could not run for this report: No resolved dependencies carried a version and a supported ecosystem
Raw JSON report machine-readable