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.
- tungsten_security_audit-0.1.0/.gitignore +13 -0
- tungsten_security_audit-0.1.0/PKG-INFO +102 -0
- tungsten_security_audit-0.1.0/README.md +88 -0
- tungsten_security_audit-0.1.0/pyproject.toml +22 -0
- tungsten_security_audit-0.1.0/src/tungsten_security_audit/__init__.py +119 -0
- tungsten_security_audit-0.1.0/src/tungsten_security_audit/checks.py +626 -0
- tungsten_security_audit-0.1.0/src/tungsten_security_audit/logins.py +146 -0
- tungsten_security_audit-0.1.0/src/tungsten_security_audit/models.py +51 -0
- tungsten_security_audit-0.1.0/src/tungsten_security_audit/net.py +79 -0
- tungsten_security_audit-0.1.0/src/tungsten_security_audit/resources.py +260 -0
- tungsten_security_audit-0.1.0/src/tungsten_security_audit/templates/tungsten_security_audit/report.html +67 -0
- tungsten_security_audit-0.1.0/tests/conftest.py +180 -0
- tungsten_security_audit-0.1.0/tests/test_security_audit.py +204 -0
|
@@ -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
|
+
]
|