JSON-Rohbericht maschinenlesbar
{
"data": {
"repo": {
"topics": [
"golang",
"ldap",
"activedirectory",
"account-management",
"password",
"active-directory",
"go",
"gopher",
"password-change",
"password-reset",
"self-service",
"netresearch",
"application"
],
"is_fork": false,
"size_kb": 3944,
"has_wiki": false,
"homepage": null,
"languages": {
"Go": 453852,
"CSS": 3181,
"HTML": 30119,
"Shell": 5669,
"Makefile": 5508,
"Dockerfile": 2082,
"JavaScript": 3338,
"TypeScript": 57336,
"Go Template": 1953
},
"pushed_at": "2026-08-01T13:56:04Z",
"created_at": "2023-06-22T12:59:00Z",
"owner_type": "Organization",
"updated_at": "2026-08-01T13:56:14Z",
"description": "A simple and fast self-service LDAP (Lightweight Directory Access Protocol) / AD (Active Directory) password changer written in Golang.",
"is_archived": false,
"is_disabled": false,
"license_spdx": "MIT",
"default_branch": "main",
"license_spdx_raw": "MIT",
"primary_language": "Go",
"significant_languages": [
"Go",
"TypeScript"
]
},
"owner": {
"blog": "https://www.netresearch.de/",
"name": "Netresearch DTT GmbH",
"type": "Organization",
"login": "netresearch",
"company": null,
"location": "Germany",
"followers": 40,
"avatar_url": "https://avatars.githubusercontent.com/u/151247?v=4",
"created_at": "2009-11-10T12:46:11Z",
"is_verified": null,
"public_repos": 280,
"account_age_days": 6109
},
"license": {
"state": "standard",
"spdx_id": "MIT",
"raw_spdx": "MIT",
"file_present": true,
"scorecard_found": true,
"profile_has_license": true
},
"activity": {
"releases": [
{
"tag": "v1.6.0",
"kind": "minor",
"published_at": "2026-07-28T22:38:06Z"
},
{
"tag": "v1.5.0",
"kind": "minor",
"published_at": "2026-07-23T09:48:03Z"
},
{
"tag": "v1.4.0",
"kind": "minor",
"published_at": "2026-07-20T23:05:28Z"
},
{
"tag": "v1.3.0",
"kind": "minor",
"published_at": "2026-04-16T14:34:19Z"
},
{
"tag": "v1.2.2",
"kind": "patch",
"published_at": "2026-03-20T06:36:46Z"
},
{
"tag": "v1.2.1",
"kind": "patch",
"published_at": "2026-03-20T06:28:44Z"
},
{
"tag": "v1.2.0",
"kind": "minor",
"published_at": "2026-02-22T12:37:59Z"
},
{
"tag": "v1.1.0",
"kind": "minor",
"published_at": "2026-02-04T20:19:23Z"
},
{
"tag": "v1.0.13",
"kind": "patch",
"published_at": "2024-11-13T09:34:13Z"
},
{
"tag": "v1.0.12",
"kind": "patch",
"published_at": "2024-10-08T15:02:49Z"
},
{
"tag": "v1.0.11",
"kind": "patch",
"published_at": "2024-07-04T13:29:14Z"
},
{
"tag": "v1.0.10",
"kind": "patch",
"published_at": "2024-01-10T15:53:19Z"
},
{
"tag": "v1.0.9",
"kind": "patch",
"published_at": "2023-09-16T00:51:39Z"
},
{
"tag": "v1.0.8",
"kind": "patch",
"published_at": "2023-08-25T10:14:42Z"
},
{
"tag": "v1.0.7",
"kind": "patch",
"published_at": "2023-07-20T15:03:44Z"
},
{
"tag": "v1.0.6",
"kind": "patch",
"published_at": "2023-07-17T08:57:05Z"
},
{
"tag": "v1.0.5",
"kind": "patch",
"published_at": "2023-07-13T09:58:54Z"
},
{
"tag": "v1.0.4",
"kind": "patch",
"published_at": "2023-07-10T07:40:05Z"
},
{
"tag": "v1.0.3",
"kind": "patch",
"published_at": "2023-07-06T14:49:19Z"
},
{
"tag": "v1.0.2",
"kind": "patch",
"published_at": "2023-07-06T14:02:40Z"
},
{
"tag": "v1.0.1",
"kind": "patch",
"published_at": "2023-07-06T12:26:43Z"
},
{
"tag": "v1.0.0",
"kind": "major",
"published_at": "2023-07-06T12:10:43Z"
}
],
"recent_commits": [
{
"oid": "86a87453ca85da28ec442b30f731a7f736724b16",
"body": "Syncs the template change from netresearch/.github#330.\n\nSecret scanning runs on **betterleaks**, which is OSS and needs no\nlicense.\nThe shared reusable declares `GITLEAKS_LICENSE` only for backwards\ncompatibility and **never reads it**, so forwarding a repo secret into\nit was\ndead plumbing that wid\n[…]\nentical to the previous template revision before\nthis\nchange, so the diff is exactly the removed `secrets:` mapping — no\nrepo-specific drift was touched. Required to keep `check-template-drift`\ngreen.",
"is_bot": false,
"headline": "ci: drop the deprecated GITLEAKS_LICENSE secret mapping (#651)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-08-01T13:56:01Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c11dccd11d3e8098b265f089994c7ed6f70ffc72",
"body": "Syncs netresearch/.github#330. Secret scanning runs on betterleaks, which is\nOSS and needs no license; the reusable declares GITLEAKS_LICENSE only for\nbackwards compatibility and never reads it. Passing the secret was dead\nplumbing that widened the exposure surface for no benefit.\n\nKeeps this file byte-identical to the template, which the drift check enforces.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "ci: drop the deprecated GITLEAKS_LICENSE secret mapping",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-08-01T13:33:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "48f4547b32f37fcc2d2ea382699b1ac5cd44a6cf",
"body": "Adds `.github/zizmor.yml`, byte-identical to the shared template config\nin\nnetresearch/.github#329.\n\nzizmor's default `unpinned-uses` policy is blanket hash-pin, so it flags\nevery\n`netresearch/*` reusable referenced `@main`. First-party reusables track\n`@main`\nby policy so fixes propagate to all con\n[…]\nnforced — the audit is not\ndisabled.\n\nThis repo is a template consumer (`.github/template.yaml`), and the\nshared\ntemplates now ship this file, so it is required to keep the template\ndrift check\ngreen.",
"is_bot": false,
"headline": "ci: add zizmor config exempting first-party reusables (#650)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-08-01T11:44:41Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "361b1d372ab782fb4dc7661d1f6aa8978fd1a2b2",
"body": "Syncs the standalone zizmor caller workflow that the go templates ship\n(netresearch/.github#327). The file was missing in this repo, which is what\nthe template drift check reports.\n\nReport-only: the reusable runs in SARIF mode and uploads to code scanning;\nit never fails CI. Minimal permissions (contents: read + security-events: write).\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "ci: add zizmor workflow from the template",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-08-01T11:01:18Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dae8e92dd166e77701699c5fa9a96a38611322ed",
"body": "zizmor's default unpinned-uses policy is blanket hash-pin, which flags every\nnetresearch/* reusable referenced @main — including the scanner jobs added in\nthis PR. First-party reusables track @main by policy so fixes propagate to all\nconsumers, so they are exempted to ref-pin here.\n\nThird-party actions remain hash-pin enforced. Byte-identical to the shared\ntemplate config (netresearch/.github#329).\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "ci: add zizmor config exempting first-party reusables",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-08-01T09:52:52Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "cfdb5591ddfd7717e459100806fccf7e9f31c4d7",
"body": "…14.0 (#648)\n\nThis PR contains the following updates:\n\n| Package | Change |\n[Age](https://docs.renovatebot.com/merge-confidence/) |\n[Confidence](https://docs.renovatebot.com/merge-confidence/) |\n|---|---|---|---|\n|\n[github.com/netresearch/simple-ldap-go](https://redirect.github.com/netresearch/simpl\n[…]\nr.mend.io/github/netresearch/ldap-selfservice-password-changer).\n\n<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->",
"is_bot": true,
"headline": "fix(deps): update module github.com/netresearch/simple-ldap-go to v1.…",
"author_name": "renovate[bot]",
"author_login": "renovate[bot]",
"committed_at": "2026-07-31T23:09:02Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a887daf37f154cecbbb58eb95cb9483c4c5e0df9",
"body": "…14.0",
"is_bot": true,
"headline": "fix(deps): update module github.com/netresearch/simple-ldap-go to v1.…",
"author_name": "renovate[bot]",
"author_login": "renovate[bot]",
"committed_at": "2026-07-31T22:57:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8c39039a60a3c687307d173418a8855e35df78b3",
"body": "This PR contains the following updates:\n\n| Package | Update | Change |\n|---|---|---|\n| [axllent/mailpit](https://mailpit.axllent.org)\n([source](https://redirect.github.com/axllent/mailpit)) | patch |\n`v1.30.5` → `v1.30.6` |\n\n---\n\n### Release Notes\n\n<details>\n<summary>axllent/mailpit (axllent/mailpit\n[…]\nr.mend.io/github/netresearch/ldap-selfservice-password-changer).\n\n<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->",
"is_bot": true,
"headline": "chore(deps): update axllent/mailpit docker tag to v1.30.6 (#647)",
"author_name": "renovate[bot]",
"author_login": "renovate[bot]",
"committed_at": "2026-07-31T04:44:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "38ce446eee1acf873426e2d01ea3b3607f8b0b45",
"body": null,
"is_bot": true,
"headline": "chore(deps): update axllent/mailpit docker tag to v1.30.6",
"author_name": "renovate[bot]",
"author_login": "renovate[bot]",
"committed_at": "2026-07-31T04:44:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "61e40277505853731d91b63f7ab86235f407e72f",
"body": "This PR contains the following updates:\n\n| Package | Change |\n[Age](https://docs.renovatebot.com/merge-confidence/) |\n[Confidence](https://docs.renovatebot.com/merge-confidence/) |\n|---|---|---|---|\n|\n[github.com/valyala/fasthttp](https://redirect.github.com/valyala/fasthttp)\n| `v1.72.0` → `v1.73.0`\n[…]\nr.mend.io/github/netresearch/ldap-selfservice-password-changer).\n\n<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->",
"is_bot": true,
"headline": "fix(deps): update module github.com/valyala/fasthttp to v1.73.0 (#646)",
"author_name": "renovate[bot]",
"author_login": "renovate[bot]",
"committed_at": "2026-07-30T18:45:38Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f7394280ee0c33359ffc2577e094c5ed2dd5a036",
"body": null,
"is_bot": true,
"headline": "fix(deps): update module github.com/valyala/fasthttp to v1.73.0",
"author_name": "renovate[bot]",
"author_login": "renovate[bot]",
"committed_at": "2026-07-30T18:44:27Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2a0817e004d94d0efeb729d363a05edfc3a15f0d",
"body": "Converts `[Unreleased]` into `## [v1.6.0] - 2026-07-28` and keeps an\nempty `[Unreleased]` above it. Minor bump because of the new branding\nfeature. No code changes.\n\nThe `[Unreleased]` entry for the numeric-overflow fix (added in #644) is\nkept and moved under the dated heading, with a PR link and a \n[…]\nrather than listed under Fixed — that code never shipped, so\nthere is nothing for an operator to have been bitten by.\n\n`bunx prettier --check CHANGELOG.md` passes. The tag follows once this\nis merged.",
"is_bot": false,
"headline": "docs(changelog): cut v1.6.0 (#645)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-28T22:30:38Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0890e68296ac637bb746e5195a973d66e7044815",
"body": "Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(changelog): cut v1.6.0",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-28T22:25:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c4d8b53d7a548e84b43e290196dde41e13d064d7",
"body": "… (#644)\n\n## Description\n\n`gosec ./...` reported four G115 findings (integer overflow conversion\n`uint` -> `int` / `int64`): `main.go:69`, `main.go:91`, `main.go:94` and\n`internal/rpchandler/request_password_reset.go:147`. Three of them\nalready carried a `#nosec` annotation claiming the value was sa\n[…]\nx G101 hardcoded-credential hits on test fixtures, one G302 on a\n`t.Cleanup` `os.Chmod(dir, 0o700)`). They predate this branch, are\nexcluded by the repo's golangci-lint config, and are untouched here.",
"is_bot": false,
"headline": "fix(options): reject numeric settings that overflow their target type…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-28T18:49:55Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1ad8106edf968145dad91866b72a3e8fbba16484",
"body": "gosec G115 flagged four uint -> int / int64 conversions. All four are real:\nnothing bounded the source values. envIntOrDefault parses with\nstrconv.ParseUint(raw, 10, strconv.IntSize) and flag.Uint accepts the same\nrange, so every one of these settings could be set to any value up to\n2^64-1 and was t\n[…]\nd by both.\n\ngo.mod pins toolchain go1.26.5: govulncheck reported 15 standard library\nadvisories against 1.26.1, all fixed in 1.26.2 or later.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(options): reject numeric settings that overflow their target type",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-28T18:30:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "4ea28cfce4943df6a23119fd5907774742191a68",
"body": "Go Report Card has been sunset — goreportcard.com now serves a farewell\npage:\n\n> After more than a decade of serving the ecosystem, Go Report Card has\nbeen sunset.\n\nThe badge therefore renders against a dead service and its link leads\nnowhere useful. Removing it.\n\nOnly the badge lines are touched; no other README content changes.\nLinting coverage is unaffected — `golangci-lint` runs in CI and is what\nthe badge was a proxy for anyway.",
"is_bot": false,
"headline": "chore(docs): drop the sunset Go Report Card badge (#643)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-28T05:54:44Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "47492e8a9d9727fad43a1a72425b63f7dfc059f5",
"body": "Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "chore(docs): drop the sunset Go Report Card badge",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-28T05:43:59Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6dfc4567abc101dbecdced8657404083a2fb29c6",
"body": "Bumps the bun group with 6 updates:\n\n| Package | From | To |\n| --- | --- | --- |\n|\n[@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss)\n| `4.3.2` | `4.3.3` |\n| [postcss](https://github.com/postcss/postcss) | `8.5.19` | `8.5.20` |\n|\n[prettier-plu\n[…]\nore\nconditions of the specified dependency\n- `@dependabot unignore <dependency name> <ignore condition>` will\nremove the ignore condition of the specified dependency and ignore\nconditions\n\n\n</details>",
"is_bot": true,
"headline": "chore(deps-dev): bump the bun group with 6 updates (#642)",
"author_name": "github-actions[bot]",
"author_login": "github-actions[bot]",
"committed_at": "2026-07-27T16:01:43Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "df6f8b1a22a51dbbd5d0f08ec5e447f8ae597321",
"body": "Bumps the bun group with 6 updates:\n\n| Package | From | To |\n| --- | --- | --- |\n| [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss) | `4.3.2` | `4.3.3` |\n| [postcss](https://github.com/postcss/postcss) | `8.5.19` | `8.5.20` |\n| [prettier-plu\n[…]\npt-eslint\n dependency-version: 8.64.0\n dependency-type: direct:development\n update-type: version-update:semver-minor\n dependency-group: bun\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>",
"is_bot": true,
"headline": "chore(deps-dev): bump the bun group with 6 updates",
"author_name": "dependabot[bot]",
"author_login": "dependabot[bot]",
"committed_at": "2026-07-27T15:59:59Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "4f2b5f405abe22ae17fb84a2739498ac7e34daa7",
"body": "…13.0 (#641)\n\nThis PR contains the following updates:\n\n| Package | Change |\n[Age](https://docs.renovatebot.com/merge-confidence/) |\n[Confidence](https://docs.renovatebot.com/merge-confidence/) |\n|---|---|---|---|\n|\n[github.com/netresearch/simple-ldap-go](https://redirect.github.com/netresearch/simpl\n[…]\nnd.io/github/netresearch/ldap-selfservice-password-changer).\n\n<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODAuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI4MC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->",
"is_bot": true,
"headline": "fix(deps): update module github.com/netresearch/simple-ldap-go to v1.…",
"author_name": "renovate[bot]",
"author_login": "renovate[bot]",
"committed_at": "2026-07-26T20:40:50Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ac5251a5d8767788da7551da90c10c9f03fe2e1c",
"body": "…13.0",
"is_bot": true,
"headline": "fix(deps): update module github.com/netresearch/simple-ldap-go to v1.…",
"author_name": "renovate[bot]",
"author_login": "renovate[bot]",
"committed_at": "2026-07-26T20:40:19Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "bf6c8fc15942972f67a846e64634316f4247f373",
"body": "…xml (#640)\n\nTwo stale values in `internal/web/static/browserconfig.xml`, both\nleftovers from the favicon generator that produced these files.\n\n## The asset path\n\n```diff\n-<square150x150logo src=\"/mstile-150x150.png\"/>\n+<square150x150logo src=\"/static/mstile-150x150.png\"/>\n```\n\nStatic assets are ser\n[…]\ne better follow-up. That is a separate decision, so this PR does\nneither. The file stays overridable through `BRANDING_DIR` (see #639),\nso a deployment that does wire it up now gets consistent values.",
"is_bot": false,
"headline": "fix(static): correct the tile asset path and colour in browserconfig.…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T16:02:09Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "eb8d7a839d0aa5b794706b949b46a1d9a8627ada",
"body": "The tile logo was referenced as `/mstile-150x150.png`, but static assets are\nserved only under the `/static` prefix (`app.Use(\"/static\", ...)` is the sole\nstatic mount in main.go), so the path resolved to nothing. Every other asset\nreference in the templates and in site.webmanifest already carries t\n[…]\n rather than making it take\neffect. It stays overridable through BRANDING_DIR, so a deployment that does\nwire it up gets consistent values.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(static): correct the tile asset path and colour in browserconfig.xml",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T15:58:39Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f881e7d89982ee6ef5de0f21e379bf336a1b722c",
"body": "…639)\n\nCloses #626.\n\nThe logo, favicon set, wordmark and browser tab title were compiled into\nthe binary, so a deployment could not meet a corporate identity\nrequirement without forking. Five optional settings now cover it.\n**Every default reproduces the previous appearance**, so an existing\ndeploym\n[…]\ntemplate` fails to escape; and the removed `aria-label` on the\nwordmark `<p>` was invalid ARIA (name prohibited on role `paragraph`),\nso dropping it is a conformance fix rather than a lost affordance.",
"is_bot": false,
"headline": "feat(branding): allow custom logo, favicons, title and attribution (#…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T15:57:53Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6febdb1f543fe28e22df712dfabcb6c6576d7833",
"body": "… announcement\n\nAddresses the findings of four independent reviews of the branding feature.\n\nSecurity — the overlay confined the request path but not the filesystem:\n\n- A symlink in the branding directory pointing at a regular file outside it\n passed validation (os.Stat reports the target's mode) a\n[…]\n survived the previous test suite are\nnow killed, as are three new ones covering the confinement, O_NONBLOCK and the\nserve-time size check.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(branding): confine the asset overlay and stop the duplicate brand…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T15:53:56Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b1e59408978ee753c5d03da16dbc05197f8e62e7",
"body": "Closes #626.\n\nThe logo, the favicon set, the wordmark and the browser tab title were\ncompiled into the binary, so a deployment could not meet a corporate identity\nrequirement without forking. Five optional settings now cover it; every\ndefault reproduces the previous appearance, so an existing deploy\n[…]\n embedded FS, and both startup refusals (unknown\nfile in the directory, cleared wordmark without alt text) abort with the\nexpected message.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(branding): allow custom logo, favicons, title and attribution",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T14:45:22Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "782aed97f2f8fab58d6653d7ec67138c8b76b383",
"body": "… (#638)\n\nResolves the two documentation contradictions left open after the v1.5.0\nrelease.\n\n## 1. Coverage threshold: three sources, three answers\n\nThe repository carried **two Codecov configurations**:\n\n| File | Target | Origin |\n| --- | --- | --- |\n| `.codecov.yml` (root) | 70% | added 2025-10-10\n[…]\nov/project` passes at `90.68% (target 80.00%)`; the\ncomponent status compares against the inflated baseline and re-baselines\non the next commit to `main`. The component check is not a required\nstatus.",
"is_bot": false,
"headline": "docs: resolve the coverage-threshold and rate-limiting contradictions…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T13:39:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "cc70f9d101d08cf0ab9983b7f40fdcc8b450cbf9",
"body": "…ix guide\n\nThe README described only the per-identifier reset limiter and called it\n\"3 requests/hour per user\". A second limiter exists and was documented\nnowhere: 10 requests per 60 minutes per IP address, at most 1000 tracked\naddresses, hardcoded in internal/ratelimit/ip_limiter.go and applied to \n[…]\nmissing. The dated assessment reports it accompanied are kept —\ntheir titles carry the date, so they read as records rather than open work.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs: document the per-IP rate limiter and drop the completed quick-f…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T13:27:56Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "bab76ba720f2fabb9f725b86d12339096f5189d8",
"body": "The repository carried two Codecov configurations with different targets:\n`.codecov.yml` in the root (70%, added 2025-10-10) and `.github/codecov.yml`\n(80%, managed by the netresearch/.github template). Codecov searches the\nrepository root before `.github/`, so the stale file won and the maintained\n\n[…]\n the effective target to 80% does not\nput the current tree at risk.\n\nAlso correct docs/development-guide.md, which repeated the 70% figure.\n\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs: remove the stale codecov config that shadowed the 80% threshold",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T13:27:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "88973c25dd7446b0ca59ebf0ddcff67acbbe5147",
"body": "## Description\n\nStamps the accumulated `[Unreleased]` CHANGELOG entries as `v1.5.0`. No\ncode changes.\n\n## Version choice — minor, not major\n\n`feat(email)!: fail-fast NewService and template-rendered reset emails`\ncarries a `!` marker and a `BREAKING CHANGE` footer, which strict\nConventional Commits \n[…]\nst`\nare accepted.\n\n## After merge\n\n`main` is tagged `v1.5.0` with a signed annotated tag; `release.yml`\nbuilds and publishes from the tag.\n\n---\n\nhttps://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM",
"is_bot": false,
"headline": "chore(release): v1.5.0 (#637)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T09:42:13Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d84cdf7349b0f2558a2cce524cc500546beb5010",
"body": "Stamp the accumulated Unreleased entries as v1.5.0.\n\nMinor, not major, despite the BREAKING CHANGE footer on\nfeat(email)!: the break is email.NewService returning\n(*Service, error), and that lives in internal/email. Go does not permit\nimporting internal/ from outside the module, so no consumer can b\n[…]\nits since v1.4.0: 23 docs, 7 fix, 7 feat, 5 test, 2 style,\n1 chore.\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "chore(release): v1.5.0",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T09:40:02Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e293453aabcc169bdc85ba35b3c6f0d6e4930097",
"body": "…penLDAP (#636)\n\n## Description\n\nBumps `github.com/netresearch/simple-ldap-go` from `v1.12.0` to\n`v1.12.2`, which fixes password writes against OpenLDAP and any other\nnon-Active-Directory server.\n\nCloses #633.\n\n## Type of Change\n\n- [x] 🐛 Bug fix (non-breaking change that fixes an issue)\n- [ ] ✨ New \n[…]\n changes required. Deployments\nagainst Active Directory are unaffected — that path still uses\n`unicodePwd` and is byte-for-byte unchanged.\n\n---\n\nhttps://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM",
"is_bot": false,
"headline": "fix(deps): bump simple-ldap-go to v1.12.2 to fix password writes on O…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T09:25:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d3e3ac5d8ae9c43c9e9e3ab614935024e6ec0daa",
"body": "…penLDAP\n\nCloses the OpenLDAP breakage reported in #633. Both password paths in the\nlibrary wrote the Active-Directory-only unicodePwd attribute, so every\nchange and reset against a non-AD directory failed with LDAP result 17\n\"Undefined Attribute Type\" — a total failure on the first attempt, with\nno\n[…]\ned reset path kept the asymmetry that exposed the bug.\n\nCloses #633\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(deps): bump simple-ldap-go to v1.12.2 to fix password writes on O…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T09:22:40Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5fb5c2c0b5cbbf59a5d1e8449aacd264c4dae5ca",
"body": "…#634)\n\n## Description\n\nFour PRs (#629, #630, #631, #632) merged into `main` in quick\nsuccession, each verified on its own branch and never together. Git\nmerged them without conflict, but \"no textual conflict\" is not\n\"coherent\" — three of them independently edited `README.md`,\n`CONTRIBUTING.md` and \n[…]\nDME.md` describes only the\nper-identifier reset limiter without mentioning the per-IP one.\n\n## Deployment Notes\n\nNone. Documentation only.\n\n---\n\nhttps://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM",
"is_bot": false,
"headline": "docs: reconcile contradictions left by four concurrently-merged PRs (…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T06:01:26Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "1a0383d1a9e29e7140703ff1aa4055a128196e2b",
"body": "…wording\n\nVerification of the drift-fix branch surfaced two residuals.\n\ninternal/AGENTS.md described integration tests as testcontainers-driven\nwith a MailHog container that starts automatically. There is no\ntestcontainers dependency in go.mod; the integration tests are behind the\n`integration` buil\n[…]\nhe mechanism was stated\nimprecisely in text this branch introduced.\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs: drop stale testcontainers claims and sharpen the config-source …",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T05:56:52Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "3b4ad9308fa97b724aca3aa03dd948feb1fa42f4",
"body": "Four merges left five documents asserting things the tree contradicts.\n\n1. Coverage tables. PROJECT-INDEX.md, CONTRIBUTING.md and\n docs/code-structure.md each carried a per-package snapshot, and every\n figure in all three was wrong: internal/email was quoted at 31.2% and\n measures 87.3%, ratel\n[…]\nlint run ./... (0\nissues), bunx prettier --check on all five files.\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs: drop stale coverage numbers and resync package docs with the tree",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T05:56:52Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "fc9b64feb8e851238da3a68a4707f33764ad2e13",
"body": "The docs advertised three knobs the code does not implement. An operator\nconfiguring any of them sees no error and believes them in effect.\n\n1. Rate limiting. AGENTS.md claimed \"3 req/hour/IP (configurable via\n RATE_LIMIT_*)\". There is no RATE_LIMIT_* prefix in internal/options/app.go,\n and inte\n[…]\nrage\nfigure, both verified against the source.\n\nNo Go code changed.\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs: remove three unimplemented capabilities from the docs",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T05:56:52Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5cbcc83ca3f0e64634782d1b000c99ffbac5732b",
"body": ".env.local.example claimed it is \"loaded AFTER the defaults, so any value you\nuncomment wins\". The opposite is true: compose.yml pins most settings inline in\nthe app service's `environment:` block, and inline values beat `env_file:`, so\n.env.local can only supply variables that block does not name. \n[…]\nontent-Type.\n- The feature added five variables, not six (34e88d7).\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs: correct env-override precedence and ADR 0003 validation claims",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-23T05:56:52Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f04e92debf5d00af36123c002bc52fc1969cf3c9",
"body": "This PR contains the following updates:\n\n| Package | Update | Change |\n|---|---|---|\n| [axllent/mailpit](https://mailpit.axllent.org)\n([source](https://redirect.github.com/axllent/mailpit)) | patch |\n`v1.30.4` → `v1.30.5` |\n\n---\n\n### Release Notes\n\n<details>\n<summary>axllent/mailpit (axllent/mailpit\n[…]\nnd.io/github/netresearch/ldap-selfservice-password-changer).\n\n<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNzUuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI3NS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->",
"is_bot": true,
"headline": "chore(deps): update axllent/mailpit docker tag to v1.30.5 (#635)",
"author_name": "renovate[bot]",
"author_login": "renovate[bot]",
"committed_at": "2026-07-23T04:50:58Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e639d6c92c3a5dd15283dac5684a1ce08f4035be",
"body": null,
"is_bot": true,
"headline": "chore(deps): update axllent/mailpit docker tag to v1.30.5",
"author_name": "renovate[bot]",
"author_login": "renovate[bot]",
"committed_at": "2026-07-23T04:50:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "7422cc4ab5b0369bf60c98517f0bb957fd3d3d0f",
"body": "…k runbook gaps (#632)\n\n## Description\n\nClears the findings that were disclosed in #629 rather than fixed, plus\ntwo runbook gaps found by actually running the merged feature against\nthe dev stack.\n\n## Type of Change\n\n- [x] 📝 Documentation update\n- [x] ✅ Test coverage improvement\n- [ ] 🐛 Bug fix\n- [ \n[…]\nves came *from* a manual end-to-end run of #629\n\n## Deployment Notes\n\nNone. Documentation and test fixtures only; no runtime code changes.\n\n---\n\nhttps://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM",
"is_bot": false,
"headline": "docs: correct AGENTS.md env vars, reserved test domains, and dev-stac…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T21:34:26Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "fd70b812256f5dcb29c46d8ba22f9f9770008af7",
"body": "… pairing\n\nDriving the email-template feature against the documented dev stack surfaced\ntwo gaps that make the runbook's own recipe fail.\n\nAPP_BASE_URL is pinned inline in compose.yml to http://localhost:3000, and\ninline values win over .env.local. Following the runbook's APP_PORT=3140\nexample there\n[…]\na template file in. Added the bind-mount recipe and the field list.\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(dev-guide): document template mounting and APP_BASE_URL/APP_PORT…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T21:07:24Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e545d795ba28eeb23e4feea710bd3984d7869793",
"body": "The email test fixtures used real, registered internet domains. RFC 2606\nreserves example.com/.net/.org and the .test/.invalid/.example TLDs so\nthat test data cannot point at somebody else's property.\n\n acme.com -> example.com\n x.com -> example.org\n evil.com -> evil.invalid\n\nx.com maps to exam\n[…]\nions were reverted.\n\nDisclosed in PR #629 and deferred at the time.\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "test(email): use RFC 2606 reserved domains in fixtures",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T20:59:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "8534e2540883b8ad3337c53ce51326d8f1e3e93f",
"body": "The \"Required environment variables\" block named eight variables the code\nnever reads, so an agent configuring a deployment from this file would\nproduce a binary that fails startup validation. Re-derived every name and\ndefault from internal/options/app.go:\n\n- LDAP_URL -> LDAP_SERVER\n- LDA\n[…]\n tags now use the real ones and the block is labelled as a pattern.\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(agents): correct environment variable names in internal/AGENTS.md",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T20:37:32Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "b2ea92ef5a9ed1de16357568a601fc3ebdae0e4f",
"body": "## Description\n\nThe password reset email was hardcoded: a `text/plain`-only body built\nin Go by `buildResetEmailBody`, the literal subject `Password Reset\nRequest`, and a bare `From:` header carrying only `SMTP_FROM_ADDRESS`.\nNothing about the message could be changed without recompiling.\n\nThis PR m\n[…]\nt be `Error` while still booting?\n - Is 1 MiB the right cap for a template file?\n\nhttps://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\n\n---\n\nhttps://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM",
"is_bot": false,
"headline": "feat(email)!: configurable reset email templates and headers (#629)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:55:43Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "c1da47135a83f90e7974f742cd78957ab6f5a3dd",
"body": "RFC 5322 section 3.6 lists orig-date as REQUIRED and permits exactly\none. The assembled message carried none: some MTAs backfill a missing\nDate and others do not, and spam filters score its absence, so a reset\nmail could be silently downranked.\n\nThe field is written with the other originator fields \n[…]\nce the service does not own, and MTAs routinely add one.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(email): add the RFC 5322 Date header to reset messages",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "63cadcc488ddfc6280482b13cf88678cf3718090",
"body": "SMTP_FROM_NAME set together with an empty SMTP_FROM_ADDRESS passed both\nstartup checks in isolation and then produced\n\n From: \"ACME IT\" <@>\n\nwhich is not an RFC 5322 addr-spec; net/mail refuses to parse it back.\n\nformatFrom now returns the empty string when the address is empty, so the\nmessage bu\n[…]\ned a name but no address learns\nthe name cannot be used.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(email): drop display name when the sender address is empty",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "17c7df0a942f484406a4815824b5345ebb8cc8da",
"body": "Three behaviours of the email-template feature had tests that passed\nagainst deleted or altered implementation code.\n\nExtract Service.buildResetMessage, everything SendResetEmail does apart\nfrom the SMTP handoff, and assert against it that ResetLink, Token,\nBaseURL, Recipient and ExpiryMinutes each \n[…]\nctTemplate, the constant the implementation substitutes.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "test(email): cover reset-message wiring, fail-fast and template guards",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "55d0ae020db22890712d0ee786013b44333d3fc8",
"body": "Independent review found the new SMTP_FROM_ADDRESS check was an upgrade\nregression. It used ValidateEmailAddress, whose regex requires a dotted\nTLD, so senders a local MTA delivers fine were refused:\n\n noreply@localhost rejected by the regex, accepted by net/mail\n gopherpass@intranet rejecte\n[…]\nted: address checks are reset-scoped, not unconditional.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(options): stop rejecting deliverable sender addresses at startup",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "849161ce3e158131325db88987960a6ca2e19df8",
"body": "ADR 0003 documented the wire value of SMTP_HEADER_OVERRIDE_X_HELPDESK_TOPIC\nas \"X-HelpDesk-Topic\". applyHeaderOverrides keys every field through\ntextproto.CanonicalMIMEHeaderKey, so the emitted name is \"X-Helpdesk-Topic\";\nREADME.md, docs/deployment.md, .env.local.example and internal/AGENTS.md\nalrea\n[…]\ns are constrained.\n\nDocumentation only; no code changed.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(adr): correct header casing, index ADR 0003, scope startup claims",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "22cda698decde382744541d2614649d52b4264ef",
"body": "Add ADR 0003 covering the configurable reset email introduced on this\nbranch: multipart/alternative with the plain-text part first, template\noverride by file path with fallback to embedded defaults, the\nEMAIL_TEMPLATE_* naming split from SMTP_* sender identity, the two\nheader layers (structured/vali\n[…]\nmoved files remain recoverable in this branch's history.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(adr): record email template design decisions",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "7e1e79f02f42e23a572dec8608e67a7cc689d60e",
"body": "Requiring the sender address when password reset is enabled was scope\ncreep from a review finding: it turned a pre-existing silent runtime\nfailure into a hard boot failure, so an existing deployment running\nwithout it would refuse to start after upgrade.\n\nRestore the previous boot behaviour for the \n[…]\neserved for bounces) and a From: header with no address.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(options): warn instead of failing when SMTP_FROM_ADDRESS is unset",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "785a63e8df9537f245391bee6d38f659d67129c9",
"body": "Fix the lint findings introduced on this branch in internal/options/app.go:\n\n- gci: move the local internal/email import into the module-prefix group,\n after the third-party group.\n- lll: wrap the 121-char SMTP_FROM_ADDRESS \"required\" message; the emitted\n string is unchanged.\n- perfsprint: replac\n[…]\no behaviour changes; all existing tests pass unmodified.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "style(options): satisfy golangci-lint in config parsing",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "59beddc1bf5acb6c93067a243eec0a7d233e8472",
"body": "Clear the ten findings the email-template work introduced in the four test\nsources. No assertion is removed, relaxed or retargeted.\n\nemail_fuzz_test.go\n- gocyclo: FuzzHeaderOverrideValidation is decomposed, not simplified. The\n header set the message may contain moves into an overrideCase value, an\n[…]\n-- fails checkParsedHeaders. Both mutants were reverted.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "test(email): satisfy golangci-lint in the email test files",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "070bc6caebc39c92788a532b4675f2195029078f",
"body": "Address the lint findings the email-template work introduced in the three\nnon-test sources, without changing any observable behavior:\n\nheaders.go\n- misspell: \"behaviour\" -> \"behavior\" (repo locale is US).\n- perfsprint: fmt.Errorf with no verbs -> errors.New.\n- intrange: byte loop uses \"for i := rang\n[…]\nse three files, gofmt is\nclean and go test ./... passes.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "style(email): satisfy golangci-lint in headers, message and render",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "af5dcae93a818015a1cd8f27c496dca91cce870e",
"body": "parseHeaderOverrides silently skipped SMTP_HEADER_OVERRIDE_=value: it was\nthe only branch in the function that degraded quietly while every sibling\nreported into ConfigError. Report it too.\n\nSMTP_FROM_ADDRESS was neither required nor validated, so with\nPASSWORD_RESET_ENABLED=true startup succeeded a\n[…]\narseHeaderOverrides) and a CR/LF-bearing SMTP_FROM_NAME.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(options): fail fast on empty header-override name and unset sender",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "493cdde3115083c15c06ddc69bbe1cf649afe962",
"body": "The header-override docs promised a header name the code does not emit and\ndescribed the To/Cc/Bcc behaviour as harmless.\n\n- applyHeaderOverrides keys fields by textproto.CanonicalMIMEHeaderKey, so\n SMTP_HEADER_OVERRIDE_X_HELPDESK_TOPIC goes on the wire as X-Helpdesk-Topic,\n not X-HelpDesk-Topic. \n[…]\n CR/LF (HTAB stays allowed) - document the actual rule.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(email): correct header-override casing and Bcc warning",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "21be0a41aa8bb05ce0f6e4219cc6687c0610764d",
"body": "FuzzHeaderOverrideValidation asserted that ValidateHeaderValue rejects\nCR/LF by re-deriving the answer with strings.ContainsAny(value, \"\\r\\n\") —\nthe same predicate the implementation uses. That assertion held for any\nimplementation, so the target could not fail.\n\nReplace it with a structural propert\n[…]\n48k execs, 110 new interesting inputs) found no crasher.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "test(email): give header-override fuzzing a real oracle",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "909418d1e68a46415752e7e75de029468abd2456",
"body": "TestBuildEmailConfig only covered the six pre-existing mappings, so the\nseven added by the email-template work (FromName, ReplyTo, ExpiryMinutes,\nSubjectTemplate, TemplateHTMLPath, TemplateTextPath, HeaderOverrides)\nwere unverified — a crossed or dropped mapping would ship silently.\n\nGive every fiel\n[…]\nder-override map contents rather\nthan just its presence.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "test(main): assert every email.Config field in buildEmailConfig test",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "d6515365918366986a4fa92c5f9902251392729d",
"body": "The message builder filtered reserved header names but passed override\nvalues through verbatim, so a HeaderOverrides value containing CRLF\nsmuggled arbitrary headers into the message. Reproduced: a value of\n\"ok\\r\\nBcc: attacker@evil.com\" emitted a live Bcc header. The options\nlayer rejects such valu\n[…]\n default templates, MIME-Version, and CRLF line endings.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(email): reject header injection and cap template reads",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "745fda0cc76ee9deafdd99b4c134fe771353f72a",
"body": "Add the new EMAIL_TEMPLATE_*, SMTP_FROM_NAME, EMAIL_REPLY_TO and\nSMTP_HEADER_OVERRIDE_* options to .env.local.example and internal docs.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(email): document custom email template and header configuration",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "18e1ffe751ddcca2148d6eaa3aedb2fd1c42f78a",
"body": "Expand buildEmailConfig to map the new template, sender-name, Reply-To,\nexpiry and header-override options. Strengthen the tag-gated integration\ntests: assert multipart structure, custom subject and header round-trip.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(main): wire email template config into email.Config",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "34e88d77221e3e3b8dd832a78e484dfea126c233",
"body": "Add EMAIL_TEMPLATE_{HTML,TEXT,SUBJECT}, SMTP_FROM_NAME, EMAIL_REPLY_TO flags\nand an SMTP_HEADER_OVERRIDE_* prefix scan. Reply-To and override names/values\nare validated at startup; structural MIME headers cannot be overridden.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(options): add email template + header-override configuration",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a9554644354380e8e1f74d047b9c3b48f65b320e",
"body": "Expand Config with sender name, Reply-To, expiry, template paths and header\noverrides. NewService now returns an error and validates templates at\nstartup; SendResetEmail renders via the template engine and sends multipart.\nRemoves the hardcoded body/subject builders.\n\nBREAKING CHANGE: email.NewService now returns (*Service, error).\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(email)!: fail-fast NewService and template-rendered reset emails",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "98adbcb6c14d57b27d08262f354dc6d1ac6b9a80",
"body": "Build text-first + HTML multipart messages with quoted-printable bodies,\nordered headers, and operator overrides applied last. The SMTP envelope\nsender is unaffected by a From-header override.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(email): assemble multipart/alternative reset emails",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "543a245c7cd2cd160fb206ad144b63a2a37d38c2",
"body": "…erge\n\nAdd ValidateHeaderName/Value (reject CR/LF and bad field-names), RFC 2047\nsubject and From-name encoding via net/mail, and deterministic header-override\napplication. Fuzz the override validators.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(email): add header validators, subject/From encoders, override m…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "bf5f62a7e46390d94dab6e6dffda34872358fb6f",
"body": "Add resetEmailData contract, embedded text/HTML defaults, and a fail-fast\nrenderer that parses and dry-runs subject/text/html templates. Uses\nExpiryMinutes instead of the previously hardcoded \"15 minutes\".\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(email): add reset-email template renderer with embedded defaults",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2017c1972e0f0b95b3befcf42e5ebbc8de503fea",
"body": "Fixes found by a multi-agent verification pass over the plan:\n\n- service_test.go step was factually wrong: it has no buildResetEmailBody\n references, and two of its three NewService sites belong to\n TestSendResetEmailValidation, which must be kept, not deleted.\n- Restructure so every commit is rep\n[…]\nTo/Cc/Bcc overrides, and the matching ConfigError cause.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(email): correct plan and spec after adversarial review",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "f52a06793009566924f3e3d93bdf6053c42f455b",
"body": "Bite-sized TDD plan (7 tasks) covering the template renderer, header\nvalidators/encoders, multipart MIME assembly, fail-fast NewService, options\nwiring with SMTP_HEADER_OVERRIDE_* prefix scan, main.go wiring, and docs.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(email): implementation plan for custom email templates (#627)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "bf14976371c431557634a15a836163e687a39a48",
"body": "Add the approved design for operator-configurable password-reset emails:\nmultipart HTML + text templates, templatable subject, structured From-name\nand Reply-To, and a raw SMTP_HEADER_OVERRIDE_* escape hatch. Fail-fast\nstartup validation; embedded defaults when unset.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(email): design spec for custom email templates (#627)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:52:10Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1efaa83380e581c4f953e06b04a5683e0b1dcdbe",
"body": "## Description\n\n`docs/development-guide.md`, added in #630, states the precedence\nbetween Compose's `env_file` and a service's inline `environment:` block\nbackwards:\n\n> Compose loads `.env.local` _after_ the inline environment, so it wins\n\nThe opposite is true: a service's `environment:` block takes\n[…]\nne block omits. That is the rule this PR documents,\nand the inverse of what the text said.\n\n## Deployment Notes\n\nNone. Documentation only.\n\n---\n\nhttps://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM",
"is_bot": false,
"headline": "docs(compose): correct env_file vs environment precedence (#631)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:51:42Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "10bf3b2e5ede877dddbf46b96dd3cf45e85ffa1a",
"body": "The local-run runbook stated that Compose loads `.env.local` after the\ninline `environment:` block \"so it wins\". The opposite holds: values in a\nservice's `environment:` block take precedence over `env_file`, which only\nsupplies variables that block does not define. Verified with Compose\nv5.3.1 agai\n[…]\ns well. Comments only; no service configuration changed.\n\nRefs #630\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(compose): correct env_file vs environment precedence",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T19:32:56Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0b76602b507e015178da3145e93874c82fd833a0",
"body": "…ain references (#630)\n\n## Description\n\nTwo related problems in the documentation.\n\nFirst, the runbook for getting the app running locally — build the\nbinary, bring up the OpenLDAP + Mailpit Compose stack, drive a reset —\nlived in `.claude/skills/run/SKILL.md`. That path is readable by one\nagent har\n[…]\n its own file under `docs/` rather than\na section of `docs/development-guide.md`?\n\nhttps://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\n\n---\n\nhttps://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM",
"is_bot": false,
"headline": "docs: consolidate the local-run runbook and replace stale pnpm toolch…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T16:04:08Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "087ed2a90afaf18451132a09c921efd4fc79474f",
"body": "- docs/onboarding.md: point at #3-configure-ldap-connection; the\n \"Enable Corepack\" step was removed on this branch and the LDAP step\n renumbered from 4 to 3. All other development-guide.md anchors in the\n repo resolve unchanged.\n- docs/testing-guide.md: the \"Pre-Commit Checks\" section claimed no\n[…]\nariable explicitly and its\n ports were already correct.\n\nRefs #627\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs: fix broken anchor, hook claim, Bun floors, Route B port",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T15:07:46Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "382387a6f54c40489bb033bb370a51662420c139",
"body": "The de-stale pass replaced \"pnpm 10.18+\" with \"Bun 1.3+\" in README, but\nthat floor was inferred from the machine's installed Bun, not read from\nthe repo. Nothing pins a Bun version: package.json sets no\npackageManager/engines, and CI passes setup-bun with the reusable\nworkflow's default of \"latest\".\n[…]\nthing. Both now state\nplainly that no version is pinned.\n\nRefs #625\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs: drop unsubstantiated Bun version floors",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T12:27:38Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "60e77ba960226104fb5043c5b04f686121fc5e24",
"body": "AGENTS.md claimed `air` was \"declared as a bun script dep\" in contrast to\ngolangci-lint. It is not: package.json has no air entry, and `bun run dev`\ninvokes it as a bare command, so it needs `go install` just like\ngolangci-lint. Following the old note left a fresh clone without air.\n\nAlso record `ma\n[…]\nist under a\ndifferent mechanism, which no doc mentioned.\n\nRefs #625\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(agents): correct the air/golangci-lint and git-hook toolchain notes",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T12:26:36Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "78c0f65ca0bcf54a1d0a3eec8664b7124340d2cc",
"body": "…e.md\n\nThe runbook moved out of the Claude-specific .claude/skills/ directory, so\nreference it from AGENTS.md where any agent or human will find it. Calls out\nthe binary-selector Dockerfile, which makes `docker compose --build` fail on a\nclean checkout until binaries exist in bin/.\n\nRefs #625\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(agents): point to the local-run runbook in docs/development-guid…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T12:24:09Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9fe88e3db77a30166323599ac5a9bb955d026545",
"body": "The repo migrated to Bun, has no Corepack pin and no hook framework, but five\ndocs still instructed readers to use pnpm, enable Corepack, and install husky.\nEvery command now matches an existing package.json script, the Makefile, or a\nreal Bun/Go invocation.\n\n- testing-guide: pnpm add/exec -> bun ad\n[…]\n\n- security-quick-fix-guide: pnpm build -> bun run build\n\nRefs #625\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs: replace stale pnpm/Corepack/husky instructions with Bun reality",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T12:22:59Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "ae5fb1050d84916ece3a6afe4d4d418fb8e89bfe",
"body": "The repo migrated from pnpm to Bun, but README, CONTRIBUTING, SECURITY,\nPROJECT-INDEX and the PR template still instructed contributors to enable\nCorepack and run pnpm. Update every instructional reference to the verified\nBun equivalents:\n\n- Drop the Corepack prerequisite entirely (Bun and Go are in\n[…]\nntions in AGENTS.md and CHANGELOG.md are left untouched.\n\nRefs #625\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs: replace pnpm/Corepack toolchain references with Bun",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T12:19:21Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "eea3714b42a85577676b2aa13767cc97cd9be112",
"body": "Fold .claude/skills/run/SKILL.md into docs/development-guide.md as a\n\"Running the Full Stack Locally\" section so the runbook is discoverable\noutside the Claude-specific directory, then delete the skill file and the\nnow-empty .claude/skills/ tree.\n\nPreserved from the runbook:\n\n- the production Docker\n[…]\nch still\ndescribed a multi-stage frontend+backend build.\n\nRefs #625\n\nClaude-Session: https://claude.ai/code/session_015JAZYAZ9LgWdjrcU9sBFqM\nSigned-off-by: Sebastian Mendel <github@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(dev-guide): merge local-run runbook and de-stale toolchain refs",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-22T12:16:52Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "4886c7930e6e3ab8bcd394461dc74fc0ad032111",
"body": "Adds a `.claude/skills/run/SKILL.md` capturing how to run GopherPass\nlocally — discovered while standing up a review instance for v1.4.0.\n\n## Why\n\nThe production `Dockerfile` is a **binary-selector** (`COPY\nbin/<repo>-linux-*`); it does no `go build`/`bun install`, so `docker\ncompose --build` fails \n[…]\n users + password policy, fixed-container-name collision fix,\nand a username-reset smoke test that proves `RESET_IDENTIFIER_MODE=both`\nsends to the registered address\n\nDocs-only; no code or CI change.",
"is_bot": false,
"headline": "docs(skill): add project run skill for local dev stack (#625)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T06:20:58Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "cfa2efdb9600a82a9813ddf5c5587dbc2ed50e3f",
"body": "Review feedback: section B's 'go run .' needs bun run build:assets first\n(go:embed fails on a clean checkout); use >> so RESET_IDENTIFIER_MODE\ndoes not clobber an existing .env.local.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(skill): build assets before native go run; append to .env.local",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T06:17:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "091460885dba43337ff1571597d5336b34a68004",
"body": "Captures the local-run recipe discovered during v1.4.0 review: the\nbinary-selector Dockerfile needs a pre-built binary in bin/ (compose\n--build fails from a clean checkout), plus the OpenLDAP+Mailpit dev\nstack, seeded credentials, the Mailpit-SMTP-internal-only gotcha, and a\nusername-reset smoke test.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(skill): add project run skill for local dev stack",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-21T06:14:20Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4020370699cd5332df567909c768ea3547c75cf1",
"body": "## Problem\n\nThe v1.4.0 tag push triggered `release.yml` but it **failed at startup\nwith no jobs** — [run\n29785209253](https://github.com/netresearch/ldap-selfservice-password-changer/actions/runs/29785209253).\nRoot cause: `netresearch/.github` consolidated its release reusables,\nremoving `create-rel\n[…]\n-verified).\n\n## After merge\n\nRe-trigger the v1.4.0 release via the backfill path (no re-tag needed —\nthe tag exists, just has no release):\n\n```\ngh workflow run release.yml --ref main -f tag=v1.4.0\n```",
"is_bot": false,
"headline": "fix(release): migrate to release-go-app orchestrator (#624)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T22:58:03Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "38c3390ca106c71b5202ad8ab4ff6d7c2e744c3d",
"body": "netresearch/.github removed the create-release.yml and finalize-release.yml\nreusables (consolidated into release-go-app.yml), so the old four-job\nrelease.yml could not resolve its reusable references and failed at\nstartup — the v1.4.0 tag push produced no release. Delegate to the\nrelease-go-app orchestrator, preserving the intentional 32-bit exclusion\n(Fiber v3 math.MaxUint32) in both the binary matrix and container platforms.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(release): migrate to release-go-app orchestrator",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T22:55:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9522c6e87dc1c512a20d552ebf3f9460adbe1e41",
"body": "Release **v1.4.0** (minor — new features, no breaking changes) covering\neverything on `main` since\n[v1.3.0](https://github.com/netresearch/ldap-selfservice-password-changer/releases/tag/v1.3.0)\n(2026-04-16).\n\n## Headline changes\n\n- **Password reset by username or email** — `RESET_IDENTIFIER_MODE`\n(`\n[…]\nsigned `v1.4.0` tag to trigger the release\npipeline (goreleaser + container + SBOM/cosign/provenance). No version\nfiles beyond `CHANGELOG.md` — the binary version is injected from the\ntag via ldflags.",
"is_bot": false,
"headline": "release: v1.4.0 (#623)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T22:48:47Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9d40bb664d8050265e96e37c450d0a111618b9bf",
"body": "Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "docs(changelog): add v1.4.0 release notes",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T22:30:48Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f44a9901842715456e5cd14c9dc418ee41c7f755",
"body": "Full upgrade pass: security/toolchain fixes, config modernization, and\nadoption of newer Go/TS/CSS features. Every commit is atomic and\nindependently gated (`go test -race`, `golangci-lint`, `tsc`, `eslint`,\nprettier).\n\n## Fixes\n\n- **brace-expansion override → 5.0.7** — closes the only open `bun\naud\n[…]\nby the go-app template (drift check); alpine:3.24.1\nis newest. Rejected as harmful: `crypto/rand.Text()` (halves token\nentropy), fiber healthcheck middleware (breaks the baked-in\n`/health/live` path).",
"is_bot": false,
"headline": "chore: dependency and code upgrade sweep (2026-07) (#622)",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T22:21:00Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "506fa1464ece758fb177bf65c4564be5fcb24335",
"body": "Add buildServerFunc/shutdownContextFunc seams (mirroring the existing\nhealthCheckFunc indirection) so run()'s listen + graceful-shutdown path\nis exercised without a real LDAP backend or process signals. run()\ncoverage 65% -> 90%; -race -count=5 stable.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "test(server): cover graceful-shutdown path in run()",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T21:23:46Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "510091e1eccca30aaaed89bcfaf3d1f89e4ad726",
"body": "The <7 guard still allowed TS 6.1-6.9, which typescript-eslint 8.x\n(peer >=4.8.4 <6.1.0) does not support — Renovate could re-break\n'bun run lint'. Pin the guard to the actual peer ceiling.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "fix(renovate): cap typescript at <6.1.0 to match typescript-eslint peer",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T21:11:04Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "01c08768995f84efd80c4ddd3ddcc891f9ef078b",
"body": "…logging\n\ncleanupExpiredLocked checks only the newest timestamp (chronological\nappend order makes it decisive) instead of scanning the slice; the\nlogged token prefix strips invalid UTF-8 that byte-slicing\nuser-supplied input can produce.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "refactor(go): apply review feedback on cleanup scan and token-prefix …",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T20:58:55Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "cd34c7fbe05e5bb036ccd0848b953f2b955c0ae9",
"body": "Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "docs: update CSS pipeline references after postcss simplification",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T20:52:55Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "23d36bb7dd3300918f982252e61f6910c25b654f",
"body": "Wraps the 15 sliding-window/expiry/cleanup tests in synctest.Test\nbubbles: time.Sleep advances a fake clock instantly and time.Now/\nNewTicker are faked consistently, making the tests deterministic\n(-count=3 -race verified) and removing real waits.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "test: use testing/synctest for time-based ratelimit and resettoken tests",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T20:52:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dff67fcc71bf9993548c525481d282f0c1f01bc4",
"body": "app.Listen now uses fiber.ListenConfig with a signal-bound\nGracefulContext and a 10s ShutdownTimeout, so in-flight requests\ncomplete before the container stops instead of being cut off.\nVerified: SIGTERM against a running instance exits cleanly within ~1s.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "feat(server): graceful shutdown on SIGINT/SIGTERM",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T20:50:51Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2c771ff7885d6d723fd76632acf4a63e31273d30",
"body": "slices.DeleteFunc reuses the backing array in the sliding-window filter\n(the manual loop allocated per request); slices.ContainsFunc replaces\nthe flag-and-break scan; min() replaces the length guard for the token\nlog prefix.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "refactor(go): use slices helpers and min builtin",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T20:48:17Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "cebdc5f9783256d405d081cb334fb1c4cac10d05",
"body": "Replaces seven while-firstChild-removeChild loops; replaceChildren() is\nBaseline widely available and within the documented Chrome 90+ browser\nfloor.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "refactor(web): use Element.replaceChildren() to clear containers",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T20:46:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "41a1216c5ca6558c64cbb685f6189be5c7cab3be",
"body": "Moves import-hygiene enforcement (consistent-type-imports) into tsc,\nwhich CI actually runs — eslint does not run in CI. erasableSyntaxOnly\nkeeps the code Node-strippable (no enums/namespaces/parameter\nproperties).\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "chore(ts): enable verbatimModuleSyntax and erasableSyntaxOnly",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T20:46:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f9e5f48cbfb8610b64066dd12f1c7cdcfb940d4c",
"body": "…ends\n\ntypescript-eslint deprecated its config() wrapper in favor of ESLint\ncore's defineConfig; the files+extends form replaces the spread-map\nworkaround for scoping the typed presets.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "refactor(lint): migrate eslint config to defineConfig with native ext…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T20:46:34Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8c002950a8c83c7945ff762dbc1c78ef8c67df44",
"body": "…ication\n\nTailwind v4 bundles Lightning CSS, covering what postcss-nested,\nautoprefixer, and cssnano did. optimize.minify must be explicit because\nbuilds do not set NODE_ENV=production. Output A/B: nesting flattened\nidentically, -webkit-backdrop-filter prefix gained, +387 bytes from\nlost selector-merging. Removes 3 devDependencies.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "chore(css): let @tailwindcss/postcss handle nesting, prefixing, minif…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T20:45:52Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6dc0cef40cb3c10fed1f3929df864339f2a63ebe",
"body": "… stylesheet\n\nTailwind v4 only loads a JS config via @config, which no CSS file\ndeclares — content/darkMode there were superseded by tailwind.css.\nprettier-plugin-tailwindcss needs tailwindStylesheet for v4 so class\nsorting sees the project's custom variants.\n\nSigned-off-by: Sebastian Mendel <info@sebastianmendel.de>",
"is_bot": false,
"headline": "chore(css): drop dead v3 tailwind.config.js, point prettier at the v4…",
"author_name": "Sebastian Mendel",
"author_login": "CybotTM",
"committed_at": "2026-07-20T20:45:28Z",
"body_truncated": false,
"is_coding_agent": false
}
],
"releases_count": 22,
"commits_last_year": 876,
"latest_release_at": "2026-07-28T22:38:06Z",
"latest_release_tag": "v1.6.0",
"releases_from_tags": false,
"days_since_last_push": 0,
"active_weeks_last_year": 51,
"days_since_latest_release": 4,
"mean_days_between_releases": 73.1
},
"community": {
"has_readme": true,
"has_license": true,
"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": "github.com/netresearch/ldap-selfservice-password-changer",
"exists": true,
"license": null,
"keywords": [],
"ecosystem": "go",
"matches_repo": true,
"registry_url": "https://pkg.go.dev/github.com/netresearch/ldap-selfservice-password-changer",
"is_deprecated": false,
"latest_version": "v1.6.0",
"repository_url": "https://github.com/netresearch/ldap-selfservice-password-changer",
"versions_count": 22,
"total_downloads": null,
"dependents_count": null,
"deprecation_note": null,
"maintainers_count": null,
"monthly_downloads": null,
"first_published_at": null,
"latest_published_at": "2026-07-28T22:30:38Z",
"latest_version_yanked": null,
"days_since_latest_publish": 4
}
]
},
"popularity": {
"forks": 0,
"stars": 31,
"watchers": 1,
"fork_history": {
"days": [],
"complete": true,
"collected": 0,
"total_forks": 0
},
"star_history": null,
"open_issues_and_prs": 2
},
"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": [
"tsconfig.json"
],
"toolchain_manifests": [
"go.mod"
],
"largest_source_bytes": 33020,
"source_files_sampled": 76,
"oversized_source_files": 0,
"agent_instruction_files": [
"AGENTS.md",
"CLAUDE.md",
"GEMINI.md",
"internal/AGENTS.md",
"internal/CLAUDE.md",
"internal/GEMINI.md",
"internal/web/AGENTS.md",
"internal/web/CLAUDE.md",
"internal/web/GEMINI.md"
],
"agent_instruction_max_bytes": 18338
},
"dependencies": {
"manifests": [
"go.mod",
"package.json"
],
"advisories": {
"error": null,
"scope": "repository_graph",
"source": "osv",
"findings": [
{
"name": "golang.org/x/crypto",
"direct": false,
"version": "v0.54.0",
"severity": "unknown",
"ecosystem": "go",
"cvss_score": null,
"advisory_ids": [
"GO-2026-5932"
],
"fixed_version": null,
"advisory_count": 1,
"oldest_advisory_days": 25
}
],
"collected": true,
"malicious": [],
"truncated": false,
"by_severity": {
"unknown": 1
},
"advisory_count": 1,
"affected_count": 1,
"assessed_count": 31,
"malicious_count": 0,
"assessed_package": null,
"unassessed_count": 12,
"direct_affected_count": 0
},
"ecosystems": [
"go",
"npm"
],
"dependencies": [
{
"name": "github.com/gofiber/fiber/v3",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v3.4.0"
},
{
"name": "github.com/joho/godotenv",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.5.1"
},
{
"name": "github.com/netresearch/simple-ldap-go",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.14.0"
},
{
"name": "github.com/stretchr/testify",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.11.1"
},
{
"name": "github.com/valyala/fasthttp",
"manifest": "go.mod",
"ecosystem": "go",
"version_constraint": "v1.73.0"
}
],
"all_dependencies": {
"error": null,
"source": "github-sbom",
"packages": [
{
"name": "github.com/gofiber/fiber/v3",
"direct": true,
"version": "v3.4.0",
"ecosystem": "go"
},
{
"name": "github.com/joho/godotenv",
"direct": true,
"version": "v1.5.1",
"ecosystem": "go"
},
{
"name": "github.com/netresearch/simple-ldap-go",
"direct": true,
"version": "v1.14.0",
"ecosystem": "go"
},
{
"name": "github.com/stretchr/testify",
"direct": true,
"version": "v1.11.1",
"ecosystem": "go"
},
{
"name": "github.com/valyala/fasthttp",
"direct": true,
"version": "v1.73.0",
"ecosystem": "go"
},
{
"name": "github.com/andybalholm/brotli",
"direct": false,
"version": "v1.2.2",
"ecosystem": "go"
},
{
"name": "github.com/azure/go-ntlmssp",
"direct": false,
"version": "v0.1.1",
"ecosystem": "go"
},
{
"name": "github.com/davecgh/go-spew",
"direct": false,
"version": "v1.1.1",
"ecosystem": "go"
},
{
"name": "github.com/docker/go-connections",
"direct": false,
"version": "v0.7.0",
"ecosystem": "go"
},
{
"name": "github.com/go-asn1-ber/asn1-ber",
"direct": false,
"version": "v1.5.8",
"ecosystem": "go"
},
{
"name": "github.com/go-ldap/ldap/v3",
"direct": false,
"version": "v3.4.14",
"ecosystem": "go"
},
{
"name": "github.com/gofiber/schema",
"direct": false,
"version": "v1.8.0",
"ecosystem": "go"
},
{
"name": "github.com/gofiber/utils/v2",
"direct": false,
"version": "v2.1.1",
"ecosystem": "go"
},
{
"name": "github.com/google/uuid",
"direct": false,
"version": "v1.6.0",
"ecosystem": "go"
},
{
"name": "github.com/klauspost/compress",
"direct": false,
"version": "v1.19.1",
"ecosystem": "go"
},
{
"name": "github.com/kr/pretty",
"direct": false,
"version": "v0.3.1",
"ecosystem": "go"
},
{
"name": "github.com/lufia/plan9stats",
"direct": false,
"version": "v0.0.0-20260330125221-c963978e514e",
"ecosystem": "go"
},
{
"name": "github.com/mattn/go-colorable",
"direct": false,
"version": "v0.1.15",
"ecosystem": "go"
},
{
"name": "github.com/mattn/go-isatty",
"direct": false,
"version": "v0.0.22",
"ecosystem": "go"
},
{
"name": "github.com/philhofer/fwd",
"direct": false,
"version": "v1.2.0",
"ecosystem": "go"
},
{
"name": "github.com/pmezard/go-difflib",
"direct": false,
"version": "v1.0.0",
"ecosystem": "go"
},
{
"name": "github.com/rogpeppe/go-internal",
"direct": false,
"version": "v1.14.1",
"ecosystem": "go"
},
{
"name": "github.com/tinylib/msgp",
"direct": false,
"version": "v1.6.4",
"ecosystem": "go"
},
{
"name": "github.com/valyala/bytebufferpool",
"direct": false,
"version": "v1.0.0",
"ecosystem": "go"
},
{
"name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
"direct": false,
"version": "v0.68.0",
"ecosystem": "go"
},
{
"name": "golang.org/x/crypto",
"direct": false,
"version": "v0.54.0",
"ecosystem": "go"
},
{
"name": "golang.org/x/net",
"direct": false,
"version": "v0.57.0",
"ecosystem": "go"
},
{
"name": "golang.org/x/sys",
"direct": false,
"version": "v0.47.0",
"ecosystem": "go"
},
{
"name": "golang.org/x/text",
"direct": false,
"version": "v0.40.0",
"ecosystem": "go"
},
{
"name": "gopkg.in/check.v1",
"direct": false,
"version": "v1.0.0-20201130134442-10cb98267c6c",
"ecosystem": "go"
},
{
"name": "gopkg.in/yaml.v3",
"direct": false,
"version": "v3.0.1",
"ecosystem": "go"
},
{
"name": "@eslint/js",
"direct": false,
"version": "^10.0.1",
"ecosystem": "npm"
},
{
"name": "@tailwindcss/postcss",
"direct": false,
"version": "^4.3.3",
"ecosystem": "npm"
},
{
"name": "eslint",
"direct": false,
"version": "^10.7.0",
"ecosystem": "npm"
},
{
"name": "eslint-config-prettier",
"direct": false,
"version": "^10.1.8",
"ecosystem": "npm"
},
{
"name": "postcss",
"direct": false,
"version": "^8.5.20",
"ecosystem": "npm"
},
{
"name": "postcss-cli",
"direct": false,
"version": "^11.0.1",
"ecosystem": "npm"
},
{
"name": "prettier",
"direct": false,
"version": "^3.9.5",
"ecosystem": "npm"
},
{
"name": "prettier-plugin-go-template",
"direct": false,
"version": "^0.0.15",
"ecosystem": "npm"
},
{
"name": "prettier-plugin-tailwindcss",
"direct": false,
"version": "^0.8.1",
"ecosystem": "npm"
},
{
"name": "tailwindcss",
"direct": false,
"version": "^4.3.3",
"ecosystem": "npm"
},
{
"name": "typescript",
"direct": false,
"version": "~7.0.2",
"ecosystem": "npm"
},
{
"name": "typescript-eslint",
"direct": false,
"version": "^8.64.0",
"ecosystem": "npm"
}
],
"collected": true,
"truncated": false,
"total_count": 43,
"direct_count": 5,
"indirect_count": 38
}
},
"maintainership": {
"issues": {
"open_prs": 0,
"merged_prs": 602,
"open_issues": 2,
"closed_ratio": 0.818,
"closed_issues": 9,
"closed_unmerged_prs": 37
},
"bus_factor": 1,
"bot_contributors": 4,
"top_contributors": [
{
"type": "User",
"login": "CybotTM",
"commits": 444,
"avatar_url": "https://avatars.githubusercontent.com/u/326348?v=4"
},
{
"type": "User",
"login": "TheDevMinerTV",
"commits": 160,
"avatar_url": "https://avatars.githubusercontent.com/u/29845135?v=4"
}
],
"contributors_sampled": 2,
"top_contributor_share": 0.735
},
"quality_signals": {
"has_ci": true,
"has_tests": true,
"ci_workflows": [
"auto-merge-deps.yml",
"check-template-drift.yml",
"ci.yml",
"codeql.yml",
"container-retention.yml",
"dependency-review.yml",
"gitleaks.yml",
"labeler.yml",
"mutation.yml",
"pr-quality.yml",
"release.yml",
"scorecard.yml",
"zizmor.yml"
],
"has_docs_dir": true,
"linter_configs": [
".golangci.yml",
"eslint.config.js"
],
"has_editorconfig": true,
"has_linter_config": true,
"has_precommit_config": false
},
"security_signals": {
"lockfiles": [
"go.sum"
],
"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": 8,
"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": "14 out of 14 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": 0,
"reason": "no effort to earn an OpenSSF best practices badge detected",
"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 9 contributing companies or organizations",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
},
{
"name": "Dangerous-Workflow",
"score": 10,
"reason": "no dangerous workflow patterns detected",
"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": 10,
"reason": "project is 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 3 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": 0,
"reason": "dependency not pinned by hash detected -- score normalized to 0",
"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": 4,
"reason": "3 out of the last 5 releases have a total of 3 signed artifacts.",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
},
{
"name": "Token-Permissions",
"score": 10,
"reason": "GitHub workflow tokens follow principle of least privilege",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
},
{
"name": "Vulnerabilities",
"score": 8,
"reason": "2 existing vulnerabilities detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
}
],
"commit": "86a87453ca85da28ec442b30f731a7f736724b16",
"ran_at": "2026-08-02T13:04:46Z",
"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,
"ci_last_run_at": "2026-08-02T03:17:19Z",
"oldest_open_prs": [],
"last_merged_pr_at": "2026-08-01T13:56:02Z",
"ci_last_conclusion": "SUCCESS",
"oldest_open_issues": [
{
"number": 10,
"created_at": "2023-08-15T09:47:51Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 628,
"created_at": "2026-07-21T13:01:43Z",
"last_comment_at": "2026-07-23T09:18:29Z",
"last_comment_author": "medykn-git"
}
]
}
},
"config": {
"disabled_metrics": [],
"disabled_categories": [],
"disabled_components": {}
},
"source": {
"url": "https://github.com/netresearch/ldap-selfservice-password-changer",
"host": "github.com",
"name": "ldap-selfservice-password-changer",
"owner": "netresearch"
},
"metrics": {
"overall": {
"key": "overall",
"band": "excellent",
"name": "Overall health",
"note": "The weighted overall 78 is calibrated to 92 on the published index scale (record calibration 2026-08-02).",
"notes": [
{
"code": "overall_calibration",
"params": {
"raw": 78,
"calibrated": 92,
"calibration": "2026-08-02"
}
}
],
"value": 92,
"inputs": {
"security": 88,
"vitality": 94,
"community": 56,
"governance": 67,
"calibration": "2026-08-02",
"engineering": 84,
"ai_readiness": 85,
"weighted_overall_raw": 78
},
"components": []
},
"categories": [
{
"key": "vitality",
"band": "exceptional",
"name": "Vitality",
"value": 94,
"weight": 0.21,
"metrics": [
{
"key": "development_activity",
"band": "exceptional",
"name": "Development activity",
"note": null,
"notes": [],
"value": 99,
"inputs": {
"commits_last_year": 876,
"human_commit_share": 0.88,
"days_since_last_push": 0,
"active_weeks_last_year": 51
},
"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": "51/52 weeks with commits",
"points": 35.3,
"status": "partial",
"details": [
{
"code": "commit_cadence_weeks",
"params": {
"weeks": 51
}
}
],
"max_points": 36
},
{
"key": "commit_volume",
"name": "Commit volume",
"detail": "876 commits in the last year",
"points": 18,
"status": "met",
"details": [
{
"code": "commits_last_year",
"params": {
"count": 876
}
}
],
"max_points": 18
},
{
"key": "openssf_scorecard_maintained",
"name": "OpenSSF Scorecard: Maintained",
"detail": "30 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 10",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
},
{
"key": "release_discipline",
"band": "excellent",
"name": "Release discipline",
"note": null,
"notes": [],
"value": 87,
"inputs": {
"releases_count": 22,
"latest_release_tag": "v1.6.0",
"releases_from_tags": false,
"days_since_latest_release": 4,
"mean_days_between_releases": 73.1
},
"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 4 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "release_recency",
"params": {
"days": 4
}
}
],
"max_points": 36
},
{
"key": "release_cadence",
"name": "Release cadence",
"detail": "a release every ~73.1 days",
"points": 19.8,
"status": "partial",
"details": [
{
"code": "release_cadence",
"params": {
"gap": 73.1
}
}
],
"max_points": 27
},
{
"key": "openssf_scorecard_signed_releases",
"name": "OpenSSF Scorecard: Signed-Releases",
"detail": "3 out of the last 5 releases have a total of 3 signed artifacts.",
"points": 4,
"status": "partial",
"details": [],
"max_points": 10
}
]
},
{
"key": "abandonment",
"band": "exceptional",
"name": "Abandonment",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"cap": null,
"state": "maintained",
"guards": [],
"signals": [],
"red_flag": false,
"multiplier_pct": 100,
"declared_reason": null,
"unverified_reason": null,
"unanswered_open_prs": null,
"unanswered_open_issues": null,
"days_since_last_merged_pr": null,
"days_since_last_human_commit": 3,
"days_since_last_human_commit_is_floor": false
},
"components": [
{
"key": "project_is_still_maintained",
"name": "Project is still maintained",
"detail": "last human commit 3 days ago",
"points": 100,
"status": "met",
"details": [
{
"code": "abandonment_maintained",
"params": {
"days": 3
}
}
],
"max_points": 100
}
]
}
],
"description": "Is the project alive — is code being written and are releases shipping?"
},
{
"key": "community",
"band": "moderate",
"name": "Community & Adoption",
"value": 56,
"weight": 0.17,
"metrics": [
{
"key": "popularity",
"band": "at_risk",
"name": "Popularity & adoption",
"note": null,
"notes": [],
"value": 24,
"inputs": {
"forks": 0,
"stars": 31,
"watchers": 1,
"growth_state": "unverified",
"growth_factor_pct": 100,
"growth_unverified_reason": "no_history"
},
"components": [
{
"key": "stars",
"name": "Stars",
"detail": "31 stars",
"points": 24,
"status": "partial",
"details": [
{
"code": "stars",
"params": {
"count": 31
}
}
],
"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": null,
"has_contributing": true,
"has_issue_template": false,
"has_code_of_conduct": true,
"readme_badge_services": [],
"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 (MIT)",
"points": 22.5,
"status": "met",
"details": [
{
"code": "license_standard",
"params": {}
},
{
"code": "license_spdx",
"params": {
"spdx": "MIT"
}
}
],
"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
}
]
}
],
"description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
},
{
"key": "governance",
"band": "good",
"name": "Sustainability & Governance",
"value": 67,
"weight": 0.23,
"metrics": [
{
"key": "maintainer_resilience",
"band": "at_risk",
"name": "Maintainer resilience (bus factor)",
"note": null,
"notes": [],
"value": 28,
"inputs": {
"bus_factor": 1,
"contributors_sampled": 2,
"top_contributor_share": 0.735
},
"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 74% of commits",
"points": 6,
"status": "partial",
"details": [
{
"code": "top_contributor_share",
"params": {
"share": 74
}
}
],
"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 9 contributing companies or organizations",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
},
{
"key": "responsiveness",
"band": "excellent",
"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": 89,
"inputs": {
"merged_prs": 602,
"open_issues": 2,
"closed_issues": 9,
"prs_merged_7d": null,
"prs_decided_7d": null,
"prs_merged_30d": null,
"prs_decided_30d": null,
"issue_closed_ratio": 0.818,
"closed_unmerged_prs": 37,
"first_time_authors_30d": null,
"first_time_prs_merged_30d": null,
"first_time_prs_decided_30d": null
},
"components": [
{
"key": "issue_resolution",
"name": "Issue resolution",
"detail": "82% of issues closed",
"points": 34.4,
"status": "partial",
"details": [
{
"code": "issues_closed_share",
"params": {
"share": 82
}
}
],
"max_points": 42
},
{
"key": "pr_acceptance",
"name": "PR acceptance",
"detail": "602/639 decided PRs merged",
"points": 28.3,
"status": "partial",
"details": [
{
"code": "decided_prs_merged",
"params": {
"merged": 602,
"decided": 639
}
}
],
"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": 67,
"inputs": {
"followers": 40,
"owner_type": "Organization",
"is_verified": null,
"owner_login": "netresearch",
"public_repos": 280,
"account_age_days": 6109
},
"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": "40 followers of netresearch",
"points": 11.6,
"status": "partial",
"details": [
{
"code": "owner_followers",
"params": {
"count": 40,
"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": [
"github.com/netresearch/ldap-selfservice-password-changer"
],
"ecosystems": "go",
"any_deprecated": false,
"min_days_since_publish": 4
},
"components": [
{
"key": "published_resolvable",
"name": "Published & resolvable",
"detail": "1 package(s) on go",
"points": 25,
"status": "met",
"details": [
{
"code": "packages_published",
"params": {
"count": 1,
"ecosystems": "go"
}
}
],
"max_points": 25
},
{
"key": "publish_recency",
"name": "Publish recency",
"detail": "latest publish 4 days ago",
"points": 35,
"status": "met",
"details": [
{
"code": "publish_recency",
"params": {
"days": 4
}
}
],
"max_points": 35
},
{
"key": "version_history",
"name": "Version history",
"detail": "22 published versions",
"points": 20,
"status": "met",
"details": [
{
"code": "published_versions",
"params": {
"count": 22
}
}
],
"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": "13 workflow(s)",
"points": 24,
"status": "met",
"details": [
{
"code": "ci_workflows",
"params": {
"count": 13
}
}
],
"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": ".golangci.yml, eslint.config.js",
"points": 16,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": ".golangci.yml, eslint.config.js"
}
}
],
"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": "14 out of 14 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": [
"golang",
"ldap",
"activedirectory",
"account-management",
"password",
"active-directory",
"go",
"gopher",
"password-change",
"password-reset",
"self-service",
"netresearch",
"application"
],
"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": "13 topics",
"points": 10,
"status": "met",
"details": [
{
"code": "topics_count",
"params": {
"count": 13
}
}
],
"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": 88,
"weight": 0.16,
"metrics": [
{
"key": "security_posture",
"band": "excellent",
"name": "Security posture",
"note": "Excluded from scoring (no data or not applicable): Packaging. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"packaging"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 85,
"inputs": {
"source": "openssf_scorecard",
"checks_evaluated": 17,
"scorecard_version": "v5.5.0",
"checks_inconclusive": 1,
"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": 6,
"status": "partial",
"details": [],
"max_points": 7.5
},
{
"key": "ci_tests",
"name": "CI-Tests",
"detail": "14 out of 14 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": "no effort to earn an OpenSSF best practices badge detected",
"points": 0,
"status": "missed",
"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 9 contributing companies or organizations",
"points": 2.5,
"status": "met",
"details": [],
"max_points": 2.5
},
{
"key": "dangerous_workflow",
"name": "Dangerous-Workflow",
"detail": "no dangerous workflow patterns detected",
"points": 10,
"status": "met",
"details": [],
"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 fuzzed",
"points": 5,
"status": "met",
"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 3 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": "dependency not pinned by hash detected -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"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": "3 out of the last 5 releases have a total of 3 signed artifacts.",
"points": 3,
"status": "partial",
"details": [],
"max_points": 7.5
},
{
"key": "token_permissions",
"name": "Token-Permissions",
"detail": "GitHub workflow tokens follow principle of least privilege",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "vulnerabilities",
"name": "Vulnerabilities",
"detail": "2 existing vulnerabilities detected",
"points": 6,
"status": "partial",
"details": [],
"max_points": 7.5
}
]
},
{
"key": "dependency_advisories",
"band": "exceptional",
"name": "Dependency advisories",
"note": "Excluded from scoring (no data or not applicable): Indirect dependencies free of known advisories, No advisories left outstanding. Remaining weights renormalized. Matched 31 resolved dependencies against OSV; 12 could not be assessed (no resolved version, an unsupported ecosystem, or beyond the reported package list). This repository publishes no package the index resolves, so the repository dependency graph was assessed instead. That graph mixes development and test pins with shipped dependencies, so only the declared runtime dependencies are scored; transitive findings are reported as context and excluded from the score. Reachability is not analyzed.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"indirect_dependencies_free_of_known_advisories",
"no_advisories_left_outstanding"
]
}
},
{
"code": "weights_renormalized",
"params": {}
},
{
"code": "advisories_scope_repository",
"params": {
"assessed": 31
}
},
{
"code": "advisories_unassessed",
"params": {
"count": 12
}
},
{
"code": "advisories_repo_graph_caveat",
"params": {}
},
{
"code": "advisories_reachability",
"params": {}
}
],
"value": 100,
"inputs": {
"source": "osv",
"advisories": 1,
"affected_packages": 1,
"assessed_packages": 31,
"unassessed_packages": 12,
"affected_by_severity": "unknown 1",
"direct_affected_packages": 0
},
"components": [
{
"key": "direct_dependencies_free_of_known_advisories",
"name": "Direct dependencies free of known advisories",
"detail": "no direct dependency carries a known advisory",
"points": 35,
"status": "met",
"details": [
{
"code": "no_direct_advisories",
"params": {}
}
],
"max_points": 35
},
{
"key": "indirect_dependencies_free_of_known_advisories",
"name": "Indirect dependencies free of known advisories",
"detail": "transitive set not separable from development and test dependencies in this scope",
"points": 0,
"status": "excluded",
"details": [
{
"code": "advisories_scope_not_separable",
"params": {}
}
],
"max_points": 25
},
{
"key": "no_advisories_left_outstanding",
"name": "No advisories left outstanding",
"detail": "no advisory carries a publication date",
"points": 0,
"status": "excluded",
"details": [
{
"code": "advisories_no_publication_date",
"params": {}
}
],
"max_points": 40
}
]
},
{
"key": "malicious_dependencies",
"band": "exceptional",
"name": "Malicious dependencies",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"source": "osv",
"meaning": "reported as a malicious package by the OpenSSF corpus; the remedy is removal or moving off the compromised name, never an upgrade of the same artifact. Versions the registry has since pulled are listed but not scored",
"packages": [],
"red_flag": false,
"assessed_packages": 31,
"malicious_packages": 0,
"direct_malicious_packages": 0,
"withdrawn_malicious_packages": 0,
"installable_malicious_packages": 0
},
"components": [
{
"key": "no_dependency_reported_as_a_malicious_package",
"name": "No dependency reported as a malicious package",
"detail": "no dependency is reported as a malicious package",
"points": 100,
"status": "met",
"details": [
{
"code": "no_malicious_dependencies",
"params": {}
}
],
"max_points": 100
}
]
},
{
"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": 5
},
"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": "excellent",
"name": "AI Readiness",
"value": 85,
"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": 1,
"agent_instruction_files": [
"AGENTS.md",
"CLAUDE.md",
"GEMINI.md",
"internal/AGENTS.md",
"internal/CLAUDE.md",
"internal/GEMINI.md",
"internal/web/AGENTS.md",
"internal/web/CLAUDE.md",
"internal/web/GEMINI.md"
],
"agent_instruction_max_bytes": 18338
},
"components": [
{
"key": "agent_instructions",
"name": "Agent instructions",
"detail": "AGENTS.md, CLAUDE.md, GEMINI.md, internal/AGENTS.md, internal/CLAUDE.md, internal/GEMINI.md, internal/web/AGENTS.md, internal/web/CLAUDE.md, internal/web/GEMINI.md",
"points": 45,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "AGENTS.md, CLAUDE.md, GEMINI.md, internal/AGENTS.md, internal/CLAUDE.md, internal/GEMINI.md, internal/web/AGENTS.md, internal/web/CLAUDE.md, internal/web/GEMINI.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": "88 of 88 human commits state their intent (structured subject or explanatory body)",
"points": 40,
"status": "met",
"details": [
{
"code": "legible_history",
"params": {
"legible": 88,
"sampled": 88
}
}
],
"max_points": 40
}
]
},
{
"key": "ai_verify_loop",
"band": "excellent",
"name": "Verify loop (build / test / typecheck)",
"note": null,
"notes": [],
"value": 80,
"inputs": {
"has_nix": false,
"has_tests": true,
"lockfiles": [
"go.sum"
],
"has_dockerfile": true,
"typed_language": true,
"bootstrap_files": [
"Makefile"
],
"has_devcontainer": false,
"has_linter_config": true,
"typecheck_configs": [
"tsconfig.json"
],
"agent_commit_share": 0,
"toolchain_manifests": [
"go.mod"
],
"dependency_bot_commit_share": 0.11
},
"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": ".golangci.yml, eslint.config.js",
"points": 11,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": ".golangci.yml, eslint.config.js"
}
}
],
"max_points": 11
},
{
"key": "static_type_checking",
"name": "Static type checking",
"detail": "tsconfig.json",
"points": 11,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "tsconfig.json"
}
}
],
"max_points": 11
},
{
"key": "reproducible_environment",
"name": "Reproducible environment",
"detail": "Dockerfile, lockfile",
"points": 10,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "Dockerfile, lockfile"
}
}
],
"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": "11 of the last 100 commits are automated dependency updates",
"points": 8,
"status": "met",
"details": [
{
"code": "dependency_bot_commits",
"params": {
"count": 11,
"sampled": 100
}
}
],
"max_points": 8
},
{
"key": "openssf_scorecard_pinned_dependencies",
"name": "OpenSSF Scorecard: Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "ai_code_legibility",
"band": "exceptional",
"name": "Code legibility for models",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"primary_language": "Go",
"largest_source_bytes": 33020,
"source_files_sampled": 76,
"oversized_source_files": 0
},
"components": [
{
"key": "type_checkable_code",
"name": "Type-checkable code",
"detail": "Go (statically typed)",
"points": 45,
"status": "met",
"details": [
{
"code": "statically_typed_language",
"params": {
"language": "Go"
}
}
],
"max_points": 45
},
{
"key": "manageable_file_sizes",
"name": "Manageable file sizes",
"detail": "0/76 source files over 60KB",
"points": 55,
"status": "met",
"details": [
{
"code": "oversized_source_files",
"params": {
"kb": 60,
"sampled": 76,
"oversized": 0
}
}
],
"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": [],
"labels": [],
"scores": {
"library": 3
},
"primary": null,
"evidence": [
{
"tier": "distribution",
"label": "library",
"source": "registry:go",
"weight": 3
}
],
"artifacts": [],
"confidence": "none",
"host_extension": false,
"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",
"Could not fetch npm package '@netresearch/ldap-selfservice-password-changer' from its registry"
],
"report_type": "repository",
"generated_at": "2026-08-02T13:05:02.748290Z",
"schema_version": "0.27.0",
"badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/n/netresearch/ldap-selfservice-password-changer.svg",
"full_name": "netresearch/ldap-selfservice-password-changer",
"license_state": "standard",
"license_spdx": "MIT"
}