tungsten-security-audit 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ frontend/node_modules/
9
+ storage/
10
+ *.db
11
+ *.sqlite3
12
+ .DS_Store
13
+ .claude/worktrees/
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.5
2
+ Name: tungsten-security-audit
3
+ Version: 0.1.0
4
+ Summary: Tungsten plugin: a security audit with a score and fix tips, plus a login log with lockout.
5
+ Project-URL: Homepage, https://tungsten.prisminfoways.com/
6
+ Project-URL: Documentation, https://tungsten.prisminfoways.com/docs/security-audit.html
7
+ Project-URL: Repository, https://github.com/Prism-Infoways/Tungsten
8
+ Author: Prism Infoways
9
+ License: MIT
10
+ Keywords: admin,audit,fastapi,lockout,login log,security,tungsten,tungsten-admin
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: tungsten-admin>=0.1.4
13
+ Description-Content-Type: text/markdown
14
+
15
+ # tungsten-security-audit
16
+
17
+ A security health check for your [Tungsten](https://tungsten.prisminfoways.com/) panel. Press **Run audit** and get a score out of 100, a grade, and a plain tip to fix each problem. It also keeps a **login log** and locks out repeated wrong passwords.
18
+
19
+ It only checks your own app. It never scans or attacks other sites.
20
+
21
+ ## What it checks
22
+
23
+ - **Settings**: login required, a strong secret key, debug mode off, HTTPS-only session cookie, `app_url` set, two-factor login, roles, open sign-up, CORS, public API docs, activity log.
24
+ - **Accounts**: how many users have full access, admins with common passwords, old password hashes, admin accounts nobody uses.
25
+ - **Logins**: lockout on, failed logins in the last 24 hours (and from which IPs), logins locked right now.
26
+ - **Website** (your own address): HTTPS, HSTS, Content Security Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, server versions in headers, CORS headers, session cookie flags (Secure, HttpOnly, SameSite), HTTP to HTTPS redirect, certificate expiry.
27
+ - **AI access**: MCP tokens nobody used for a long time (with `tungsten-mcp`).
28
+ - **Packages**: Python version still getting security fixes, and (when you tick it) installed packages with known holes from [osv.dev](https://osv.dev).
29
+
30
+ Every audit is saved, so **Audit history** shows how your score changes.
31
+
32
+ ## Login log and lockout
33
+
34
+ Every login try is logged: email, IP, browser, and the result (logged in, wrong password, unknown email, locked out...). After 5 wrong passwords for one email from one IP, that login is locked for 15 minutes. Unlock it early from the **Login log** screen.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install tungsten-security-audit
40
+ ```
41
+
42
+ ```python
43
+ from tungsten_security_audit import SecurityAuditPlugin
44
+
45
+ panel = Panel(..., app_url="https://admin.example.com")
46
+ panel.plugin(SecurityAuditPlugin())
47
+ panel.create_tables(engine)
48
+ panel.mount(app)
49
+ ```
50
+
51
+ This adds **Security audit**, **Audit history** and **Login log** under a **Security** group. It needs `tungsten-admin` 0.1.4 or newer.
52
+
53
+ ## Options
54
+
55
+ ```python
56
+ SecurityAuditPlugin(
57
+ lockout=True, # lock a login after too many wrong passwords
58
+ max_failures=5, # wrong passwords before the lock
59
+ lockout_minutes=15, # how long the lock lasts
60
+ keep_login_days=90, # delete login log rows older than this (0 keeps them)
61
+ trust_proxy=False, # read the IP from X-Forwarded-For (only behind your own proxy)
62
+ max_superusers=3, # warn when more users have full access
63
+ idle_days=90, # warn about admins and AI tokens unused this long
64
+ check_passwords=True, # try common passwords against admin password hashes
65
+ allowed_hosts=[], # more host names the website check may open
66
+ checks=[], # your own checks
67
+ )
68
+ ```
69
+
70
+ With roles on, only users with the **Open Security audit** permission see the audit, and **See the login log** for the log. Super admins see both.
71
+
72
+ ## Your own checks
73
+
74
+ ```python
75
+ from tungsten_security_audit import Result
76
+
77
+ def check_backups(audit):
78
+ ok = Path("/backups/latest.sql.gz").exists()
79
+ return Result("backups", "Settings", "Database backups exist", "pass" if ok else "fail", "high",
80
+ "Found today's backup." if ok else "No backup found.",
81
+ "" if ok else "Set up a daily backup job.")
82
+
83
+ panel.plugin(SecurityAuditPlugin(checks=[check_backups]))
84
+ ```
85
+
86
+ ## License
87
+
88
+ MIT
89
+
90
+ ## More Tungsten plugins
91
+
92
+ | Package | What it adds | Docs |
93
+ | --- | --- | --- |
94
+ | [`tungsten-leads`](https://pypi.org/project/tungsten-leads/) | Leads list, stages, timeline and your own lead form fields | [README](https://github.com/Prism-Infoways/Tungsten/tree/claude/tungsten-admin-panel/plugins/leads) |
95
+ | [`tungsten-meta-leads`](https://pypi.org/project/tungsten-meta-leads/) | Facebook and Instagram lead form leads, with one-click setup | [Guide](https://tungsten.prisminfoways.com/docs/facebook-leads.html) |
96
+ | [`tungsten-whatsapp`](https://pypi.org/project/tungsten-whatsapp/) | WhatsApp for leads: click-to-chat, Cloud API or WhatsApp Web | [Guide](https://tungsten.prisminfoways.com/docs/whatsapp.html) |
97
+ | [`tungsten-mcp`](https://pypi.org/project/tungsten-mcp/) | MCP server, so AI assistants like Claude can read and change your data | [Guide](https://tungsten.prisminfoways.com/docs/mcp.html) |
98
+ | [`tungsten-tickets`](https://pypi.org/project/tungsten-tickets/) | Help desk: tickets, replies, notes, SLA and a customer support page | [Guide](https://tungsten.prisminfoways.com/docs/tickets.html) |
99
+ | [`tungsten-blog`](https://pypi.org/project/tungsten-blog/) | Blog built for SEO, GEO and AEO, with a live score, sitemap and llms.txt | [Guide](https://tungsten.prisminfoways.com/docs/blog.html) |
100
+ | [`tungsten-seo-audit`](https://pypi.org/project/tungsten-seo-audit/) | SEO audit of your website: score, fix tips, AI search checks, history | [Guide](https://tungsten.prisminfoways.com/docs/seo-audit.html) |
101
+
102
+ Core package: [`tungsten-admin`](https://pypi.org/project/tungsten-admin/). Website and docs: https://tungsten.prisminfoways.com/
@@ -0,0 +1,88 @@
1
+ # tungsten-security-audit
2
+
3
+ A security health check for your [Tungsten](https://tungsten.prisminfoways.com/) panel. Press **Run audit** and get a score out of 100, a grade, and a plain tip to fix each problem. It also keeps a **login log** and locks out repeated wrong passwords.
4
+
5
+ It only checks your own app. It never scans or attacks other sites.
6
+
7
+ ## What it checks
8
+
9
+ - **Settings**: login required, a strong secret key, debug mode off, HTTPS-only session cookie, `app_url` set, two-factor login, roles, open sign-up, CORS, public API docs, activity log.
10
+ - **Accounts**: how many users have full access, admins with common passwords, old password hashes, admin accounts nobody uses.
11
+ - **Logins**: lockout on, failed logins in the last 24 hours (and from which IPs), logins locked right now.
12
+ - **Website** (your own address): HTTPS, HSTS, Content Security Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, server versions in headers, CORS headers, session cookie flags (Secure, HttpOnly, SameSite), HTTP to HTTPS redirect, certificate expiry.
13
+ - **AI access**: MCP tokens nobody used for a long time (with `tungsten-mcp`).
14
+ - **Packages**: Python version still getting security fixes, and (when you tick it) installed packages with known holes from [osv.dev](https://osv.dev).
15
+
16
+ Every audit is saved, so **Audit history** shows how your score changes.
17
+
18
+ ## Login log and lockout
19
+
20
+ Every login try is logged: email, IP, browser, and the result (logged in, wrong password, unknown email, locked out...). After 5 wrong passwords for one email from one IP, that login is locked for 15 minutes. Unlock it early from the **Login log** screen.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ pip install tungsten-security-audit
26
+ ```
27
+
28
+ ```python
29
+ from tungsten_security_audit import SecurityAuditPlugin
30
+
31
+ panel = Panel(..., app_url="https://admin.example.com")
32
+ panel.plugin(SecurityAuditPlugin())
33
+ panel.create_tables(engine)
34
+ panel.mount(app)
35
+ ```
36
+
37
+ This adds **Security audit**, **Audit history** and **Login log** under a **Security** group. It needs `tungsten-admin` 0.1.4 or newer.
38
+
39
+ ## Options
40
+
41
+ ```python
42
+ SecurityAuditPlugin(
43
+ lockout=True, # lock a login after too many wrong passwords
44
+ max_failures=5, # wrong passwords before the lock
45
+ lockout_minutes=15, # how long the lock lasts
46
+ keep_login_days=90, # delete login log rows older than this (0 keeps them)
47
+ trust_proxy=False, # read the IP from X-Forwarded-For (only behind your own proxy)
48
+ max_superusers=3, # warn when more users have full access
49
+ idle_days=90, # warn about admins and AI tokens unused this long
50
+ check_passwords=True, # try common passwords against admin password hashes
51
+ allowed_hosts=[], # more host names the website check may open
52
+ checks=[], # your own checks
53
+ )
54
+ ```
55
+
56
+ With roles on, only users with the **Open Security audit** permission see the audit, and **See the login log** for the log. Super admins see both.
57
+
58
+ ## Your own checks
59
+
60
+ ```python
61
+ from tungsten_security_audit import Result
62
+
63
+ def check_backups(audit):
64
+ ok = Path("/backups/latest.sql.gz").exists()
65
+ return Result("backups", "Settings", "Database backups exist", "pass" if ok else "fail", "high",
66
+ "Found today's backup." if ok else "No backup found.",
67
+ "" if ok else "Set up a daily backup job.")
68
+
69
+ panel.plugin(SecurityAuditPlugin(checks=[check_backups]))
70
+ ```
71
+
72
+ ## License
73
+
74
+ MIT
75
+
76
+ ## More Tungsten plugins
77
+
78
+ | Package | What it adds | Docs |
79
+ | --- | --- | --- |
80
+ | [`tungsten-leads`](https://pypi.org/project/tungsten-leads/) | Leads list, stages, timeline and your own lead form fields | [README](https://github.com/Prism-Infoways/Tungsten/tree/claude/tungsten-admin-panel/plugins/leads) |
81
+ | [`tungsten-meta-leads`](https://pypi.org/project/tungsten-meta-leads/) | Facebook and Instagram lead form leads, with one-click setup | [Guide](https://tungsten.prisminfoways.com/docs/facebook-leads.html) |
82
+ | [`tungsten-whatsapp`](https://pypi.org/project/tungsten-whatsapp/) | WhatsApp for leads: click-to-chat, Cloud API or WhatsApp Web | [Guide](https://tungsten.prisminfoways.com/docs/whatsapp.html) |
83
+ | [`tungsten-mcp`](https://pypi.org/project/tungsten-mcp/) | MCP server, so AI assistants like Claude can read and change your data | [Guide](https://tungsten.prisminfoways.com/docs/mcp.html) |
84
+ | [`tungsten-tickets`](https://pypi.org/project/tungsten-tickets/) | Help desk: tickets, replies, notes, SLA and a customer support page | [Guide](https://tungsten.prisminfoways.com/docs/tickets.html) |
85
+ | [`tungsten-blog`](https://pypi.org/project/tungsten-blog/) | Blog built for SEO, GEO and AEO, with a live score, sitemap and llms.txt | [Guide](https://tungsten.prisminfoways.com/docs/blog.html) |
86
+ | [`tungsten-seo-audit`](https://pypi.org/project/tungsten-seo-audit/) | SEO audit of your website: score, fix tips, AI search checks, history | [Guide](https://tungsten.prisminfoways.com/docs/seo-audit.html) |
87
+
88
+ Core package: [`tungsten-admin`](https://pypi.org/project/tungsten-admin/). Website and docs: https://tungsten.prisminfoways.com/
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.24"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tungsten-security-audit"
7
+ version = "0.1.0"
8
+ description = "Tungsten plugin: a security audit with a score and fix tips, plus a login log with lockout."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Prism Infoways" }]
13
+ keywords = ["tungsten", "tungsten-admin", "security", "audit", "login log", "lockout", "fastapi", "admin"]
14
+ dependencies = ["tungsten-admin>=0.1.4"]
15
+
16
+ [project.urls]
17
+ Homepage = "https://tungsten.prisminfoways.com/"
18
+ Documentation = "https://tungsten.prisminfoways.com/docs/security-audit.html"
19
+ Repository = "https://github.com/Prism-Infoways/Tungsten"
20
+
21
+ [tool.hatch.build.targets.wheel]
22
+ packages = ["src/tungsten_security_audit"]
@@ -0,0 +1,119 @@
1
+ """A security health check for your Tungsten panel, plus a login log with lockout.
2
+
3
+ from tungsten_security_audit import SecurityAuditPlugin
4
+
5
+ panel.plugin(SecurityAuditPlugin())
6
+ panel.create_tables(engine)
7
+ panel.mount(app)
8
+
9
+ Open "Security audit" in the panel and press Run audit. It checks your own app only (settings,
10
+ admin accounts, logins, your site's HTTPS and headers, and optionally your packages against
11
+ osv.dev) and gives a score with a tip to fix each problem. Every login try is logged, and
12
+ repeated wrong passwords lock that login for a while.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from collections.abc import Iterable
18
+ from pathlib import Path
19
+ from typing import Any
20
+ from collections.abc import Callable
21
+
22
+ from tungsten import Plugin
23
+
24
+ from .checks import DEFAULT_CHECKS, Audit, Result, grade_of, run_checks, score_of
25
+ from .logins import LoginGuard, client_ip
26
+ from .models import LoginAttempt, SecurityAudit, SecurityBase
27
+ from .net import Net
28
+ from .resources import (
29
+ AUDIT_PERMISSION,
30
+ LOGINS_PERMISSION,
31
+ LoginAttemptResource,
32
+ SecurityAuditPage,
33
+ SecurityAuditResource,
34
+ run_audit,
35
+ )
36
+
37
+ __version__ = "0.1.0"
38
+
39
+
40
+ class SecurityAuditPlugin(Plugin):
41
+ """``SecurityAuditPlugin(lockout=True, max_failures=5, lockout_minutes=15, ...)``.
42
+
43
+ - ``lockout``: after ``max_failures`` wrong passwords for one email from one IP, that login is
44
+ locked for ``lockout_minutes``. Admins can unlock it on the Login log screen.
45
+ - ``log_logins``: keep a log of every login try. ``keep_login_days``: how long (0 keeps them all).
46
+ - ``trust_proxy``: read the visitor's IP from ``X-Forwarded-For``. Only behind a proxy you run.
47
+ - ``max_superusers``: more users with full access than this gives a warning.
48
+ - ``idle_days``: admin accounts and AI tokens unused this long give a warning.
49
+ - ``check_passwords``: try a short list of common passwords against each admin's password hash.
50
+ - ``public_url``: your site's address for the website check. Default: the panel's ``app_url``.
51
+ - ``allowed_hosts``: more host names the website check may open (e.g. your main site).
52
+ - ``checks``: extra checks of your own, ``fn(audit) -> Result | list[Result] | None``.
53
+ """
54
+
55
+ id = "security-audit"
56
+ metadata = SecurityBase.metadata
57
+ templates = Path(__file__).with_name("templates")
58
+
59
+ def __init__(self, *, lockout: bool = True, max_failures: int = 5, lockout_minutes: int = 15,
60
+ log_logins: bool = True, keep_login_days: int = 90, trust_proxy: bool = False,
61
+ max_superusers: int = 3, idle_days: int = 90, check_passwords: bool = True,
62
+ public_url: str | None = None, allowed_hosts: Iterable[str] = (),
63
+ checks: Iterable[Callable[[Audit], Any]] = (), net: Net | None = None) -> None:
64
+ self.lockout = lockout
65
+ self.max_failures = max(1, max_failures)
66
+ self.lockout_minutes = max(1, lockout_minutes)
67
+ self.log_logins = log_logins or lockout # the lockout counts the logged failures
68
+ self.keep_login_days = keep_login_days
69
+ self.trust_proxy = trust_proxy
70
+ self.max_superusers = max_superusers
71
+ self.idle_days = idle_days
72
+ self.check_passwords = check_passwords
73
+ self.public_url = public_url.rstrip("/") if public_url else None
74
+ self.allowed_hosts = [h.lower() for h in allowed_hosts]
75
+ self.checks = list(DEFAULT_CHECKS) + list(checks)
76
+ self.net = net or Net()
77
+ self.guard = LoginGuard(self)
78
+ self.panel: Any = None
79
+ #: your main app, seen by ``panel.mount(app)``, for the debug, CORS and API docs checks
80
+ self.app: Any = None
81
+
82
+ def register(self, panel: Any) -> None:
83
+ self.panel = panel
84
+ panel.pages([SecurityAuditPage])
85
+ panel.resources([SecurityAuditResource])
86
+ if panel.auth.enabled:
87
+ panel.resources([LoginAttemptResource])
88
+ if self.log_logins:
89
+ self.guard.install(panel.auth)
90
+
91
+ def mount(self, app: Any, panel: Any) -> None:
92
+ self.app = app
93
+
94
+ def permissions(self) -> list[tuple[str, str]]:
95
+ return [(LOGINS_PERMISSION, "See the login log")] # the audit page lists its own
96
+
97
+
98
+ __all__ = [
99
+ "AUDIT_PERMISSION",
100
+ "DEFAULT_CHECKS",
101
+ "LOGINS_PERMISSION",
102
+ "Audit",
103
+ "LoginAttempt",
104
+ "LoginAttemptResource",
105
+ "LoginGuard",
106
+ "Net",
107
+ "Result",
108
+ "SecurityAudit",
109
+ "SecurityAuditPage",
110
+ "SecurityAuditPlugin",
111
+ "SecurityAuditResource",
112
+ "SecurityBase",
113
+ "__version__",
114
+ "client_ip",
115
+ "grade_of",
116
+ "run_audit",
117
+ "run_checks",
118
+ "score_of",
119
+ ]