{"id":50412059,"url":"https://github.com/srkyn/relic","last_synced_at":"2026-05-31T04:03:32.866Z","repository":{"id":355965888,"uuid":"1230440815","full_name":"srkyn/relic","owner":"srkyn","description":"Surface stale and orphaned objects in on-premises Active Directory.","archived":false,"fork":false,"pushed_at":"2026-05-06T02:23:23.000Z","size":20,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-06T04:29:58.373Z","etag":null,"topics":["active-directory","audit","cli","identity","ldap","python","security","sysadmin","windows"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/srkyn.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-06T02:08:49.000Z","updated_at":"2026-05-06T02:23:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/srkyn/relic","commit_stats":null,"previous_names":["srkyn/relic"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/srkyn/relic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srkyn%2Frelic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srkyn%2Frelic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srkyn%2Frelic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srkyn%2Frelic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/srkyn","download_url":"https://codeload.github.com/srkyn/relic/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/srkyn%2Frelic/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33718463,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["active-directory","audit","cli","identity","ldap","python","security","sysadmin","windows"],"created_at":"2026-05-31T04:03:31.052Z","updated_at":"2026-05-31T04:03:32.851Z","avatar_url":"https://github.com/srkyn.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"![relic project banner](docs/assets/relic-banner.svg)\n\n# relic\n\nSurface stale and orphaned objects in on-premises Active Directory.\n\nActive Directory environments accumulate objects that nobody is actively managing. Users leave, their accounts stay. Service accounts outlive the projects that created them. Workstations get decommissioned but their computer objects stay in the directory, still in scope for group policy and still appearing in security group memberships. relic connects to a domain controller over LDAP and tells you what's there, how old it is, and what risk it carries.\n\n![Release](https://img.shields.io/github/v/release/srkyn/relic?style=flat-square)\n![CI](https://img.shields.io/github/actions/workflow/status/srkyn/relic/ci.yml?branch=main\u0026style=flat-square)\n![Python](https://img.shields.io/badge/python-3.8%2B-1f6feb?style=flat-square)\n![License](https://img.shields.io/github/license/srkyn/relic?style=flat-square)\n\n## Demo\n\n![Sanitized relic terminal output](docs/assets/relic-sample-output.svg)\n\nIn a synthetic 200-object Active Directory test environment, relic surfaced 12 HIGH findings: 4 disabled accounts still holding group memberships, 3 service accounts with SPNs and passwords unchanged beyond 365 days, and 5 computer accounts inactive beyond one year. An additional 9 MEDIUM findings covered accounts with non-expiring passwords and dormant users. Total scan time under 8 seconds over a local LDAP connection.\n\nMore context is available in [docs/demo.md](docs/demo.md).\n\n## What It Finds\n\n**Stale computer accounts** — computers that have not authenticated since the threshold. Computer accounts authenticate when the machine starts and periodically during operation. A computer that has been silent for months either no longer exists or is no longer receiving policy.\n\n**Dormant user accounts** — users who have not logged in within the threshold window. Includes service accounts identified by the presence of `servicePrincipalName`.\n\n**Disabled accounts with group memberships** — the most immediately actionable finding. The account is already disabled, but its group memberships are still live. Reviewing and removing unnecessary memberships closes a re-enablement path.\n\n**Accounts with non-expiring passwords** — `DONT_EXPIRE_PASSWORD` set in `userAccountControl`. Flagged on its own as MEDIUM; escalates to HIGH when the account is also stale.\n\n**Service accounts with aging passwords** — accounts with SPNs whose `pwdLastSet` is older than the threshold are flagged as Kerberoasting exposure. A service account with an SPN and a multi-year-old password is a target regardless of whether the account appears active.\n\n## Scan Flow\n\n```mermaid\nflowchart LR\n    LDAP[\"Domain controller\u003cbr/\u003eLDAP or LDAPS bind\"] --\u003e Objects[\"Directory objects\u003cbr/\u003eusers, computers, disabled accounts, service accounts\"]\n    Objects --\u003e Signals[\"Hygiene signals\u003cbr/\u003elast logon, password age, SPN, groups, flags\"]\n    Signals --\u003e Risk[\"Risk rules\u003cbr/\u003emedium or high findings\"]\n    Risk --\u003e Reports[\"Review output\u003cbr/\u003eterminal table, JSON, CSV\"]\n    Reports --\u003e Cleanup[\"Operator cleanup\u003cbr/\u003eremove memberships, rotate passwords, disable stale objects\"]\n```\n\n## Risk Levels\n\n| Condition | Severity |\n|---|---|\n| Disabled account still holds group memberships | HIGH |\n| Service account (SPN), password unchanged \u003e365 days | HIGH |\n| Computer account inactive \u003e365 days | HIGH |\n| Non-expiring password + inactive \u003e180 days | HIGH |\n| Stale user or computer account | MEDIUM |\n| Non-expiring password (active account) | MEDIUM |\n| Disabled account, no group memberships | MEDIUM |\n| Service account password unchanged \u003e90 days | MEDIUM |\n\n## Usage\n\n```bash\n# Full scan — all object types\nrl -s dc01.corp.local --domain corp.local\nrelic --server dc01.corp.local --domain corp.local\n\n# Show only HIGH and MEDIUM findings\nrl -s dc01.corp.local --domain corp.local -F\nrelic --server dc01.corp.local --domain corp.local --only-flagged\n\n# Adjust the inactivity threshold\nrl -s dc01.corp.local --domain corp.local -d 180\n\n# Target specific object types\nrl -s dc01.corp.local --domain corp.local -U --disabled\n\n# Write reports\nrl -s dc01.corp.local --domain corp.local -o results.json --output-csv results.csv\n\n# Use LDAPS and an explicit base DN\nrl -s dc01.corp.local --ssl --base-dn \"DC=corp,DC=local\"\n\n# Disable flagged objects (dry run first)\nrl -s dc01.corp.local --domain corp.local --disable -n\nrl -s dc01.corp.local --domain corp.local --disable\n```\n\n## Authentication\n\n```bash\n# Simple bind (username + password from environment)\nexport RELIC_BIND_PASSWORD=\"\u003cpass\u003e\"\nrl -s dc01.corp.local --domain corp.local -u svc_relic --password-env RELIC_BIND_PASSWORD\n\n# NTLM\nrl -s dc01.corp.local -u \"CORP\\svc_relic\" --password-env RELIC_BIND_PASSWORD --base-dn \"DC=corp,DC=local\"\n```\n\nPowerShell equivalent:\n\n```powershell\n$env:RELIC_BIND_PASSWORD = \"\u003cpass\u003e\"\nrl -s dc01.corp.local --domain corp.local -u svc_relic --password-env RELIC_BIND_PASSWORD\n```\n\nLDAPS (`--ssl` or `--port 636`) is recommended to avoid transmitting credentials in cleartext. `-P` still works for quick local testing, but `--password-env` or `--password-stdin` is safer for repeatable runs.\n\n## Short Flags\n\n| Short | Long | Description |\n|---|---|---|\n| `-s HOST` | `--server HOST` | Domain controller hostname or IP |\n| `-u USER` | `--username USER` | Bind username |\n| `-P PASS` | `--password PASS` | Bind password |\n|  | `--password-env VAR` | Read bind password from environment variable |\n|  | `--password-stdin` | Read bind password from stdin |\n| `-p N` | `--port N` | LDAP port (default: 389) |\n| `-d N` | `--days N` | Inactivity threshold in days |\n| `-U` | `--users` | Scan user accounts |\n| `-C` | `--computers` | Scan computer accounts |\n| `-o FILE` | `--output FILE` | Write JSON report to FILE |\n| `-F` | `--only-flagged` | Show MEDIUM and HIGH only |\n| `-q` | `--quiet` | Summary only, no table |\n| `-n` | `--dry-run` | Report without making changes |\n\n## Scan Flags\n\n| Flag | What it scans |\n|---|---|\n| *(default — no flags)* | All object types |\n| `--users` | Dormant user accounts |\n| `--computers` | Stale computer accounts |\n| `--disabled` | Disabled accounts with group memberships |\n| `--never-expires` | Accounts with non-expiring passwords |\n\n## Output Fields\n\nJSON and CSV output includes: `name`, `type`, `dn`, `last_logon_str`, `age_days`, `pwd_age_days`, `disabled`, `never_expires`, `group_count`, `risk`, `risk_reasons`, `os`, `description`.\n\n## Installation\n\n```bash\ngit clone https://github.com/srkyn/relic.git\ncd relic\npip install .\nrl --version\n```\n\nOr run directly:\n\n```bash\npip install ldap3 tabulate\npython relic.py --server dc01.corp.local --domain corp.local --dry-run\n```\n\n## Comparison to lapse\n\nrelic and [lapse](https://github.com/srkyn/lapse) solve the same class of problem in different environments. lapse targets Entra ID (cloud) via Microsoft Graph API. relic targets on-premises Active Directory via LDAP. Between them they cover identity hygiene across both environments.\n\n## Files\n\n- `relic.py`: the scanner\n- `docs/demo.md`: sanitized example output and interpretation\n- `tests/test_relic.py`: unit tests (48 cases)\n- `docs/design-notes.md`: implementation details and limitations\n- `CHANGELOG.md`: release history\n\n## Limitations\n\n- `lastLogonTimestamp` replication lag means age readings may be off by up to 14 days.\n- Does not evaluate nested group membership chains.\n- Does not parse password policy to determine actual expiry status.\n- Does not support Kerberos/GSSAPI authentication.\n- May miss objects in OUs the bind account cannot read.\n- Read-only except when `--disable` is used.\n\n## Testing\n\n```bash\npython -m py_compile relic.py\npython -m unittest discover -s tests -v\nrl --version\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsrkyn%2Frelic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsrkyn%2Frelic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsrkyn%2Frelic/lists"}