janus-sec 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.
Files changed (38) hide show
  1. janus_sec-0.1.0/.github/workflows/tests.yml +30 -0
  2. janus_sec-0.1.0/.gitignore +218 -0
  3. janus_sec-0.1.0/LICENSE +21 -0
  4. janus_sec-0.1.0/PKG-INFO +168 -0
  5. janus_sec-0.1.0/README.md +156 -0
  6. janus_sec-0.1.0/janus_sec/__init__.py +0 -0
  7. janus_sec-0.1.0/janus_sec/audit.py +84 -0
  8. janus_sec-0.1.0/janus_sec/checks/__init__.py +0 -0
  9. janus_sec-0.1.0/janus_sec/checks/allowlist.toml +16 -0
  10. janus_sec-0.1.0/janus_sec/checks/context.py +54 -0
  11. janus_sec-0.1.0/janus_sec/checks/group_ownership.py +118 -0
  12. janus_sec-0.1.0/janus_sec/checks/identity.py +38 -0
  13. janus_sec-0.1.0/janus_sec/checks/ownership.py +47 -0
  14. janus_sec-0.1.0/janus_sec/checks/symlinks.py +71 -0
  15. janus_sec-0.1.0/janus_sec/checks/world_permissions.py +75 -0
  16. janus_sec-0.1.0/janus_sec/cli.py +184 -0
  17. janus_sec-0.1.0/janus_sec/config.py +87 -0
  18. janus_sec-0.1.0/janus_sec/fix.py +94 -0
  19. janus_sec-0.1.0/janus_sec/models.py +79 -0
  20. janus_sec-0.1.0/janus_sec/platform_detect.py +79 -0
  21. janus_sec-0.1.0/janus_sec/scanner.py +132 -0
  22. janus_sec-0.1.0/janus_sec/targets.py +66 -0
  23. janus_sec-0.1.0/janus_sec/tui.py +201 -0
  24. janus_sec-0.1.0/pyproject.toml +28 -0
  25. janus_sec-0.1.0/tests/__init__.py +0 -0
  26. janus_sec-0.1.0/tests/test_audit.py +68 -0
  27. janus_sec-0.1.0/tests/test_cli.py +268 -0
  28. janus_sec-0.1.0/tests/test_config.py +160 -0
  29. janus_sec-0.1.0/tests/test_context.py +64 -0
  30. janus_sec-0.1.0/tests/test_fix.py +105 -0
  31. janus_sec-0.1.0/tests/test_group_ownership.py +105 -0
  32. janus_sec-0.1.0/tests/test_ownership.py +37 -0
  33. janus_sec-0.1.0/tests/test_platform_detect.py +60 -0
  34. janus_sec-0.1.0/tests/test_scanner.py +119 -0
  35. janus_sec-0.1.0/tests/test_symlinks.py +66 -0
  36. janus_sec-0.1.0/tests/test_targets.py +58 -0
  37. janus_sec-0.1.0/tests/test_tui.py +174 -0
  38. janus_sec-0.1.0/tests/test_world_permissions.py +75 -0
@@ -0,0 +1,30 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install package and test dependencies
25
+ run: |
26
+ pip install -e .
27
+ pip install pytest pytest-asyncio
28
+
29
+ - name: Run tests
30
+ run: pytest -v
@@ -0,0 +1,218 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A Sivasubramanian Manoj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.4
2
+ Name: janus-sec
3
+ Version: 0.1.0
4
+ Summary: A terminal-first credential exposure auditor
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: textual>=0.58
9
+ Requires-Dist: tomli-w>=1.0
10
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
11
+ Description-Content-Type: text/markdown
12
+
13
+ # janus-sec
14
+
15
+ ![Tests](https://github.com/A-S-Manoj/janus-sec/actions/workflows/tests.yml/badge.svg)
16
+
17
+ **A terminal-first credential exposure auditor.**
18
+
19
+ Your SSH keys, AWS credentials, and kube config are only as safe as their file
20
+ permissions. A `chmod 644` typo, a bad tarball extraction, or a cloud sync
21
+ tool can quietly leave a private key readable by every user on your machine
22
+ — and there's usually no warning until something goes wrong.
23
+
24
+ `janus-sec` scans the credential files you already have, tells you exactly
25
+ what's wrong and why it matters, and fixes it with one confirmed keystroke.
26
+ Nothing leaves your machine — no network calls, no file content is ever
27
+ read, no `sudo`.
28
+
29
+ ```
30
+ $ janus-sec
31
+ Scanned 4 file(s), 8 not present.
32
+
33
+ [HIGH] /home/user/.ssh/id_rsa
34
+ Issue: world_readable (mode 644)
35
+ Why: This file is readable by any local user on this machine.
36
+ Credential files should only be readable by their owner.
37
+ Fix: chmod 600 /home/user/.ssh/id_rsa
38
+
39
+ [MEDIUM] /home/user/.aws/credentials
40
+ Issue: group_readable (mode 640)
41
+ Why: This file is readable by group 'docker', which is not your
42
+ primary group. Other accounts in that group can read this file.
43
+ Fix: chmod 600 /home/user/.aws/credentials
44
+
45
+ 2 finding(s) total, 1 HIGH risk.
46
+ ```
47
+
48
+ ## Why this exists
49
+
50
+ The underlying check here is genuinely simple — `find ~/.ssh -perm /077`
51
+ does most of the mechanical work in one line. That's not the point.
52
+ The gap `janus-sec` closes isn't "how do I check this," it's "I never
53
+ thought to check this, and if I did, I wouldn't trust a one-off command
54
+ to tell me what to do about it or keep a record that I did it."
55
+
56
+ Concretely, what a one-liner doesn't give you:
57
+ - **An explanation.** `-rw-r--r--` means nothing to most people at a
58
+ glance. "This private key is readable by any local user on this
59
+ machine" changes behavior.
60
+ - **The right fix for each file type.** SSH keys, `known_hosts`, AWS
61
+ credentials, and kube configs don't all want the same target mode.
62
+ - **A safe way to apply it.** Confirmation by default, `--dry-run` to
63
+ preview, and a re-check immediately before writing (the file you
64
+ looked at when scanning might not be the file you're about to change).
65
+ - **A record.** An append-only audit log of every fix, so "what did I
66
+ change and when" has a real answer.
67
+ - **A CI gate.** `--ci` exits non-zero on any HIGH-risk finding, so this
68
+ can fail a pipeline, not just print a warning nobody reads.
69
+
70
+ ## What it checks
71
+
72
+ | Check | Risk | Auto-fix? |
73
+ |---|---|---|
74
+ | World-readable / world-writable | HIGH | Yes |
75
+ | Owned by a different user | HIGH | No — would require `sudo`, out of scope |
76
+ | Group-readable (non-primary group) | MEDIUM | Yes |
77
+ | Symlink pointing outside its expected directory | MEDIUM | No — target choice is a judgment call |
78
+
79
+ Scans by default: `~/.ssh/*`, `~/.aws/*`, `~/.kube/config`, `~/.npmrc`,
80
+ `~/.git-credentials`, `~/.docker/config.json`. Missing files are silently
81
+ skipped, not errors — most people won't have all of these.
82
+
83
+ ## Safety properties
84
+
85
+ - **No network access, ever.**
86
+ - **Never reads file contents** — only metadata (permissions, ownership).
87
+ It can't tell you if a secret has leaked; it tells you if the file is
88
+ more exposed than it should be.
89
+ - **Never elevates privileges.** No `sudo`, no `chown`. If a fix would
90
+ require that, `janus-sec` reports the problem and stops — it doesn't
91
+ attempt it.
92
+ - **Every fix requires confirmation** (or an explicit `--yes` for
93
+ scripted use), and `--dry-run` previews changes with zero side effects.
94
+ - **Append-only audit log** of every fix applied, at
95
+ `~/.local/state/janus-sec/audit.log`.
96
+
97
+ ## Install
98
+
99
+ Not yet published to PyPI — install from source for now:
100
+
101
+ ```bash
102
+ git clone https://github.com/A-S-Manoj/janus-sec.git
103
+ cd janus-sec
104
+ python3 -m venv .venv && source .venv/bin/activate
105
+ pip install -e .
106
+ ```
107
+
108
+ ## Usage
109
+
110
+ ```bash
111
+ # Scan (also the default with no arguments - read-only, safe to run any time)
112
+ janus-sec
113
+ janus-sec scan
114
+
115
+ # Interactive TUI
116
+ janus-sec tui
117
+
118
+ # Machine-readable output
119
+ janus-sec scan --format json
120
+
121
+ # CI mode: exit 1 if any HIGH-risk finding exists
122
+ janus-sec scan --ci
123
+
124
+ # Fix everything fixable, with confirmation
125
+ janus-sec fix
126
+
127
+ # Fix one specific file
128
+ janus-sec fix ~/.ssh/id_rsa
129
+
130
+ # Preview fixes without changing anything
131
+ janus-sec fix --dry-run
132
+
133
+ # Skip the confirmation prompt (for scripts)
134
+ janus-sec fix --yes
135
+ ```
136
+
137
+ ## Configuration
138
+
139
+ `~/.config/janus-sec/config.toml` (optional — everything works with no
140
+ config file at all):
141
+
142
+ ```toml
143
+ # Suppress a specific check on a specific file - not the whole file,
144
+ # just that one issue, so other real problems on it still get caught.
145
+ [[ignore]]
146
+ path = "/home/user/.ssh/known_hosts"
147
+ check_type = "group_readable"
148
+ note = "shared dev box, group access is intentional"
149
+
150
+ # Treat a group as known-safe, machine-wide.
151
+ [[allowlist]]
152
+ group = "wheel"
153
+ action = "suppress" # or "downgrade_to_low"
154
+ note = "trusted admin group on my machines"
155
+ ```
156
+
157
+ ## Development
158
+
159
+ Same setup as above, plus test dependencies:
160
+
161
+ ```bash
162
+ pip install pytest pytest-asyncio
163
+ pytest -v
164
+ ```
165
+
166
+ ## License
167
+
168
+ MIT
@@ -0,0 +1,156 @@
1
+ # janus-sec
2
+
3
+ ![Tests](https://github.com/A-S-Manoj/janus-sec/actions/workflows/tests.yml/badge.svg)
4
+
5
+ **A terminal-first credential exposure auditor.**
6
+
7
+ Your SSH keys, AWS credentials, and kube config are only as safe as their file
8
+ permissions. A `chmod 644` typo, a bad tarball extraction, or a cloud sync
9
+ tool can quietly leave a private key readable by every user on your machine
10
+ — and there's usually no warning until something goes wrong.
11
+
12
+ `janus-sec` scans the credential files you already have, tells you exactly
13
+ what's wrong and why it matters, and fixes it with one confirmed keystroke.
14
+ Nothing leaves your machine — no network calls, no file content is ever
15
+ read, no `sudo`.
16
+
17
+ ```
18
+ $ janus-sec
19
+ Scanned 4 file(s), 8 not present.
20
+
21
+ [HIGH] /home/user/.ssh/id_rsa
22
+ Issue: world_readable (mode 644)
23
+ Why: This file is readable by any local user on this machine.
24
+ Credential files should only be readable by their owner.
25
+ Fix: chmod 600 /home/user/.ssh/id_rsa
26
+
27
+ [MEDIUM] /home/user/.aws/credentials
28
+ Issue: group_readable (mode 640)
29
+ Why: This file is readable by group 'docker', which is not your
30
+ primary group. Other accounts in that group can read this file.
31
+ Fix: chmod 600 /home/user/.aws/credentials
32
+
33
+ 2 finding(s) total, 1 HIGH risk.
34
+ ```
35
+
36
+ ## Why this exists
37
+
38
+ The underlying check here is genuinely simple — `find ~/.ssh -perm /077`
39
+ does most of the mechanical work in one line. That's not the point.
40
+ The gap `janus-sec` closes isn't "how do I check this," it's "I never
41
+ thought to check this, and if I did, I wouldn't trust a one-off command
42
+ to tell me what to do about it or keep a record that I did it."
43
+
44
+ Concretely, what a one-liner doesn't give you:
45
+ - **An explanation.** `-rw-r--r--` means nothing to most people at a
46
+ glance. "This private key is readable by any local user on this
47
+ machine" changes behavior.
48
+ - **The right fix for each file type.** SSH keys, `known_hosts`, AWS
49
+ credentials, and kube configs don't all want the same target mode.
50
+ - **A safe way to apply it.** Confirmation by default, `--dry-run` to
51
+ preview, and a re-check immediately before writing (the file you
52
+ looked at when scanning might not be the file you're about to change).
53
+ - **A record.** An append-only audit log of every fix, so "what did I
54
+ change and when" has a real answer.
55
+ - **A CI gate.** `--ci` exits non-zero on any HIGH-risk finding, so this
56
+ can fail a pipeline, not just print a warning nobody reads.
57
+
58
+ ## What it checks
59
+
60
+ | Check | Risk | Auto-fix? |
61
+ |---|---|---|
62
+ | World-readable / world-writable | HIGH | Yes |
63
+ | Owned by a different user | HIGH | No — would require `sudo`, out of scope |
64
+ | Group-readable (non-primary group) | MEDIUM | Yes |
65
+ | Symlink pointing outside its expected directory | MEDIUM | No — target choice is a judgment call |
66
+
67
+ Scans by default: `~/.ssh/*`, `~/.aws/*`, `~/.kube/config`, `~/.npmrc`,
68
+ `~/.git-credentials`, `~/.docker/config.json`. Missing files are silently
69
+ skipped, not errors — most people won't have all of these.
70
+
71
+ ## Safety properties
72
+
73
+ - **No network access, ever.**
74
+ - **Never reads file contents** — only metadata (permissions, ownership).
75
+ It can't tell you if a secret has leaked; it tells you if the file is
76
+ more exposed than it should be.
77
+ - **Never elevates privileges.** No `sudo`, no `chown`. If a fix would
78
+ require that, `janus-sec` reports the problem and stops — it doesn't
79
+ attempt it.
80
+ - **Every fix requires confirmation** (or an explicit `--yes` for
81
+ scripted use), and `--dry-run` previews changes with zero side effects.
82
+ - **Append-only audit log** of every fix applied, at
83
+ `~/.local/state/janus-sec/audit.log`.
84
+
85
+ ## Install
86
+
87
+ Not yet published to PyPI — install from source for now:
88
+
89
+ ```bash
90
+ git clone https://github.com/A-S-Manoj/janus-sec.git
91
+ cd janus-sec
92
+ python3 -m venv .venv && source .venv/bin/activate
93
+ pip install -e .
94
+ ```
95
+
96
+ ## Usage
97
+
98
+ ```bash
99
+ # Scan (also the default with no arguments - read-only, safe to run any time)
100
+ janus-sec
101
+ janus-sec scan
102
+
103
+ # Interactive TUI
104
+ janus-sec tui
105
+
106
+ # Machine-readable output
107
+ janus-sec scan --format json
108
+
109
+ # CI mode: exit 1 if any HIGH-risk finding exists
110
+ janus-sec scan --ci
111
+
112
+ # Fix everything fixable, with confirmation
113
+ janus-sec fix
114
+
115
+ # Fix one specific file
116
+ janus-sec fix ~/.ssh/id_rsa
117
+
118
+ # Preview fixes without changing anything
119
+ janus-sec fix --dry-run
120
+
121
+ # Skip the confirmation prompt (for scripts)
122
+ janus-sec fix --yes
123
+ ```
124
+
125
+ ## Configuration
126
+
127
+ `~/.config/janus-sec/config.toml` (optional — everything works with no
128
+ config file at all):
129
+
130
+ ```toml
131
+ # Suppress a specific check on a specific file - not the whole file,
132
+ # just that one issue, so other real problems on it still get caught.
133
+ [[ignore]]
134
+ path = "/home/user/.ssh/known_hosts"
135
+ check_type = "group_readable"
136
+ note = "shared dev box, group access is intentional"
137
+
138
+ # Treat a group as known-safe, machine-wide.
139
+ [[allowlist]]
140
+ group = "wheel"
141
+ action = "suppress" # or "downgrade_to_low"
142
+ note = "trusted admin group on my machines"
143
+ ```
144
+
145
+ ## Development
146
+
147
+ Same setup as above, plus test dependencies:
148
+
149
+ ```bash
150
+ pip install pytest pytest-asyncio
151
+ pytest -v
152
+ ```
153
+
154
+ ## License
155
+
156
+ MIT
File without changes
@@ -0,0 +1,84 @@
1
+ """Append-only audit log of every fix applied.
2
+
3
+ Deliberately plain text, one line per entry - meant to be human-readable
4
+ (tail -f it, grep it) rather than a structured format, since machine-
5
+ readable output already exists separately via --format json on scan.
6
+
7
+ Never logs file CONTENTS, only the operation metadata: what path, what
8
+ mode changed to what, when, and how (cli vs tui).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ from dataclasses import dataclass
15
+ from datetime import datetime, timezone
16
+ from pathlib import Path
17
+
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class AuditLogEntry:
21
+ timestamp: str # ISO 8601 UTC
22
+ path: str
23
+ before_mode_octal: str
24
+ after_mode_octal: str
25
+ applied_via: str # "cli" | "tui"
26
+
27
+ def to_line(self) -> str:
28
+ return (
29
+ f"{self.timestamp} chmod {self.path} "
30
+ f"{self.before_mode_octal} -> {self.after_mode_octal} "
31
+ f"(via {self.applied_via})"
32
+ )
33
+
34
+
35
+ def default_log_path() -> Path:
36
+ # XDG state directory - the conventional home for append-only logs
37
+ # and other "history of what happened" data, distinct from config
38
+ # (XDG_CONFIG_HOME) and cache (XDG_CACHE_HOME).
39
+ xdg_state = os.environ.get("XDG_STATE_HOME")
40
+ base = Path(xdg_state) if xdg_state else Path.home() / ".local" / "state"
41
+ return base / "janus-sec" / "audit.log"
42
+
43
+
44
+ def append_entry(
45
+ path: str,
46
+ before_mode_octal: str,
47
+ after_mode_octal: str,
48
+ applied_via: str,
49
+ log_path: Path | None = None,
50
+ ) -> AuditLogEntry:
51
+ """Append one entry to the audit log, creating the log file/directory
52
+ if this is the first entry ever written.
53
+ """
54
+ if log_path is None:
55
+ log_path = default_log_path()
56
+
57
+ entry = AuditLogEntry(
58
+ timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"),
59
+ path=path,
60
+ before_mode_octal=before_mode_octal,
61
+ after_mode_octal=after_mode_octal,
62
+ applied_via=applied_via,
63
+ )
64
+
65
+ log_path.parent.mkdir(parents=True, exist_ok=True)
66
+ with open(log_path, "a", encoding="utf-8") as f:
67
+ f.write(entry.to_line() + "\n")
68
+
69
+ return entry
70
+
71
+
72
+ def read_entries(log_path: Path | None = None) -> list[str]:
73
+ """Read raw log lines, most recent last (natural file order).
74
+ Returns an empty list if the log doesn't exist yet - no entries
75
+ logged is not an error.
76
+ """
77
+ if log_path is None:
78
+ log_path = default_log_path()
79
+
80
+ if not log_path.exists():
81
+ return []
82
+
83
+ with open(log_path, encoding="utf-8") as f:
84
+ return [line.rstrip("\n") for line in f if line.strip()]
File without changes
@@ -0,0 +1,16 @@
1
+ # Known-benign group-ownership patterns.
2
+ #
3
+ # Each [[pattern]] suppresses (or downgrades) a group-readable finding
4
+ # when the file's group matches `group` (and, if given, the OS matches
5
+ # `os`). Left empty for now - only add entries once verified against
6
+ # real machines, not speculatively.
7
+ #
8
+ # Example of the shape an entry will take, once verified:
9
+ #
10
+ # [[pattern]]
11
+ # os = "linux"
12
+ # group = "wheel"
13
+ # action = "suppress" # "suppress" or "downgrade_to_low"
14
+ # note = "why this is considered safe"
15
+
16
+ patterns = []