audnet 0.1.2__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 (54) hide show
  1. audnet-0.1.2/.github/ISSUE_TEMPLATE/bug_report.md +28 -0
  2. audnet-0.1.2/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
  3. audnet-0.1.2/.github/PULL_REQUEST_TEMPLATE.md +24 -0
  4. audnet-0.1.2/.github/labeler.yml +72 -0
  5. audnet-0.1.2/.github/workflows/auto-close-issues.yml +43 -0
  6. audnet-0.1.2/.github/workflows/ci.yml +101 -0
  7. audnet-0.1.2/.github/workflows/issue-labeler.yml +74 -0
  8. audnet-0.1.2/.github/workflows/labeler.yml +21 -0
  9. audnet-0.1.2/.github/workflows/publish.yml +68 -0
  10. audnet-0.1.2/.github/workflows/size-label.yml +26 -0
  11. audnet-0.1.2/.gitignore +27 -0
  12. audnet-0.1.2/.pre-commit-config.yaml +36 -0
  13. audnet-0.1.2/CHANGELOG.md +86 -0
  14. audnet-0.1.2/CONTRIBUTING.md +122 -0
  15. audnet-0.1.2/LICENSE +21 -0
  16. audnet-0.1.2/PKG-INFO +826 -0
  17. audnet-0.1.2/README.md +790 -0
  18. audnet-0.1.2/SECURITY.md +121 -0
  19. audnet-0.1.2/benchmarks/bench_collectors.py +176 -0
  20. audnet-0.1.2/pyproject.toml +107 -0
  21. audnet-0.1.2/src/audnet/__init__.py +8 -0
  22. audnet-0.1.2/src/audnet/cli.py +222 -0
  23. audnet-0.1.2/src/audnet/collector.py +180 -0
  24. audnet-0.1.2/src/audnet/collector_async.py +199 -0
  25. audnet-0.1.2/src/audnet/compliance.py +250 -0
  26. audnet-0.1.2/src/audnet/config.py +113 -0
  27. audnet-0.1.2/src/audnet/exceptions.py +25 -0
  28. audnet-0.1.2/src/audnet/models.py +105 -0
  29. audnet-0.1.2/src/audnet/parser.py +67 -0
  30. audnet-0.1.2/src/audnet/reporter.py +61 -0
  31. audnet-0.1.2/src/audnet/templates/__init__.py +0 -0
  32. audnet-0.1.2/src/audnet/textfsm_templates/__init__.py +0 -0
  33. audnet-0.1.2/src/audnet/textfsm_templates/cisco_ios_show_cdp_neighbors_detail.textfsm +12 -0
  34. audnet-0.1.2/src/audnet/textfsm_templates/cisco_ios_show_interface_status.textfsm +10 -0
  35. audnet-0.1.2/src/audnet/textfsm_templates/cisco_ios_show_ip_interface_brief.textfsm +10 -0
  36. audnet-0.1.2/src/audnet/textfsm_templates/cisco_ios_show_running_config.textfsm +4 -0
  37. audnet-0.1.2/src/audnet/textfsm_templates/cisco_ios_show_version.textfsm +9 -0
  38. audnet-0.1.2/src/audnet/vendor_registry.py +165 -0
  39. audnet-0.1.2/tests/__init__.py +1 -0
  40. audnet-0.1.2/tests/conftest.py +34 -0
  41. audnet-0.1.2/tests/test_cli.py +973 -0
  42. audnet-0.1.2/tests/test_collector.py +709 -0
  43. audnet-0.1.2/tests/test_collector_async.py +206 -0
  44. audnet-0.1.2/tests/test_compliance.py +847 -0
  45. audnet-0.1.2/tests/test_config.py +250 -0
  46. audnet-0.1.2/tests/test_exceptions.py +39 -0
  47. audnet-0.1.2/tests/test_integration.py +259 -0
  48. audnet-0.1.2/tests/test_logging.py +105 -0
  49. audnet-0.1.2/tests/test_models.py +160 -0
  50. audnet-0.1.2/tests/test_parser.py +181 -0
  51. audnet-0.1.2/tests/test_reporter.py +102 -0
  52. audnet-0.1.2/tests/test_vendor_registry.py +153 -0
  53. audnet-0.1.2/tests/test_version.py +11 -0
  54. audnet-0.1.2/uv.lock +1579 -0
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ labels: bug
5
+ ---
6
+
7
+ **Describe the bug**
8
+ A clear and concise description of what the bug is.
9
+
10
+ **To Reproduce**
11
+ Steps to reproduce the behavior:
12
+ 1. Run `audnet ...`
13
+ 2. ...
14
+ 3. See error
15
+
16
+ **Expected behavior**
17
+ A clear and concise description of what you expected to happen.
18
+
19
+ **Screenshots**
20
+ If applicable, add screenshots.
21
+
22
+ **Environment:**
23
+ - OS: [e.g. Ubuntu 24.04]
24
+ - Python: [e.g. 3.12]
25
+ - audnet: [e.g. v0.1.0]
26
+
27
+ **Additional context**
28
+ Add any other context about the problem here.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project
4
+ labels: enhancement
5
+ ---
6
+
7
+ **Is your feature request related to a problem? Please describe.**
8
+ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9
+
10
+ **Describe the solution you'd like**
11
+ A clear and concise description of what you want to happen.
12
+
13
+ **Describe alternatives you've considered**
14
+ A clear and concise description of any alternative solutions or features you've considered.
15
+
16
+ **Additional context**
17
+ Add any other context or screenshots about the feature request here.
@@ -0,0 +1,24 @@
1
+ ## Description
2
+
3
+ <!-- What does this PR do? -->
4
+
5
+ ## Related Issues
6
+
7
+ <!-- Link issues that this PR closes. Use one of: -->
8
+ <!-- Closes #123, Fixes #456, Resolves #789 -->
9
+
10
+ ## Type of Change
11
+
12
+ - [ ] Bug fix (non-breaking change that fixes an issue)
13
+ - [ ] New feature (non-breaking change that adds functionality)
14
+ - [ ] Breaking change (fix or feature that would cause existing functionality to change)
15
+ - [ ] Documentation update
16
+ - [ ] CI / tooling
17
+
18
+ ## Checklist
19
+
20
+ - [ ] Tests added or updated
21
+ - [ ] Lint + type checks pass (`ruff check src/ tests/`, `mypy src/`)
22
+ - [ ] Full test suite passes
23
+ - [ ] CHANGELOG.md updated if applicable
24
+ - [ ] README.md updated if applicable
@@ -0,0 +1,72 @@
1
+ # Auto-label configuration for PRs based on changed paths
2
+ # Format for actions/labeler@v5
3
+
4
+ enhancement:
5
+ - changed-files:
6
+ - any-glob-to-any-file:
7
+ - src/**/*
8
+ - '!src/**/.github/**/*'
9
+
10
+ bug:
11
+ - changed-files:
12
+ - any-glob-to-any-file:
13
+ - src/**/*
14
+ - '!src/**/.github/**/*'
15
+ - '**/fix*'
16
+ - '**/bug*'
17
+
18
+ chore:
19
+ - changed-files:
20
+ - any-glob-to-any-file:
21
+ - .github/**/*
22
+ - '*.lock'
23
+ - uv.lock
24
+
25
+ documentation:
26
+ - changed-files:
27
+ - any-glob-to-any-file:
28
+ - '**/*.md'
29
+ - docs/**/*
30
+ - .github/ISSUE_TEMPLATE/**/*
31
+
32
+ security:
33
+ - changed-files:
34
+ - any-glob-to-any-file:
35
+ - src/audnet/reporter.py
36
+ - src/audnet/collector.py
37
+ - pyproject.toml
38
+
39
+ collector:
40
+ - changed-files:
41
+ - any-glob-to-any-file:
42
+ - src/audnet/collector.py
43
+
44
+ cli:
45
+ - changed-files:
46
+ - any-glob-to-any-file:
47
+ - src/audnet/cli.py
48
+
49
+ compliance:
50
+ - changed-files:
51
+ - any-glob-to-any-file:
52
+ - src/audnet/compliance.py
53
+
54
+ parser:
55
+ - changed-files:
56
+ - any-glob-to-any-file:
57
+ - src/audnet/parser.py
58
+
59
+ models:
60
+ - changed-files:
61
+ - any-glob-to-any-file:
62
+ - src/audnet/models.py
63
+
64
+ config:
65
+ - changed-files:
66
+ - any-glob-to-any-file:
67
+ - src/audnet/config.py
68
+
69
+ tests:
70
+ - changed-files:
71
+ - any-glob-to-any-file:
72
+ - tests/**/*
@@ -0,0 +1,43 @@
1
+ name: Auto Close Issues
2
+
3
+ on:
4
+ pull_request:
5
+ types: [closed]
6
+
7
+ jobs:
8
+ close-linked-issues:
9
+ if: github.event.pull_request.merged == true
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ issues: write
13
+ pull-requests: read
14
+ steps:
15
+ - uses: actions/github-script@v7
16
+ with:
17
+ script: |
18
+ const pr = context.payload.pull_request;
19
+ const body = pr.body || '';
20
+
21
+ const issueRegex = /(?:close[sd]?|fix(?:es|ed)?|resolve[sd]?)\s+#(\d+)/gi;
22
+ const matches = [...body.matchAll(issueRegex)];
23
+
24
+ if (matches.length === 0) {
25
+ console.log('No linked issues found in PR body.');
26
+ return;
27
+ }
28
+
29
+ for (const match of matches) {
30
+ const issueNumber = parseInt(match[1]);
31
+ try {
32
+ await github.rest.issues.update({
33
+ owner: context.repo.owner,
34
+ repo: context.repo.repo,
35
+ issue_number: issueNumber,
36
+ state: 'closed',
37
+ state_reason: 'completed'
38
+ });
39
+ console.log(`Closed issue #${issueNumber}`);
40
+ } catch (e) {
41
+ console.log(`Could not close #${issueNumber}: ${e.message}`);
42
+ }
43
+ }
@@ -0,0 +1,101 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master, main]
6
+ pull_request:
7
+ branches: [master, main]
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ lint:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.14"
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v5
26
+ with:
27
+ version: "latest"
28
+
29
+ - name: Create venv and install dependencies
30
+ run: |
31
+ uv venv
32
+ uv pip install -e ".[dev]"
33
+
34
+ - name: Lint with ruff
35
+ run: uv run ruff check src/ tests/
36
+
37
+ - name: Type check with mypy
38
+ run: uv run mypy src/
39
+
40
+ security:
41
+ needs: lint
42
+ runs-on: ubuntu-latest
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+
46
+ - name: Set up Python
47
+ uses: actions/setup-python@v5
48
+ with:
49
+ python-version: "3.14"
50
+
51
+ - name: Install uv
52
+ uses: astral-sh/setup-uv@v5
53
+ with:
54
+ version: "latest"
55
+
56
+ - name: Create venv and install dependencies
57
+ run: |
58
+ uv venv
59
+ uv pip install -e ".[dev]"
60
+
61
+ - name: Run Bandit security scan
62
+ run: uv run bandit -r src/ -c pyproject.toml
63
+
64
+ - name: Run pip-audit dependency check
65
+ uses: nick-fields/retry@v3
66
+ with:
67
+ timeout_minutes: 5
68
+ max_attempts: 3
69
+ retry_wait_seconds: 30
70
+ command: uv run pip-audit --ignore-vuln CVE-2026-44405 --ignore-vuln PYSEC-2026-196
71
+
72
+ test:
73
+ needs: security
74
+ runs-on: ubuntu-latest
75
+ strategy:
76
+ matrix:
77
+ python-version: ["3.12", "3.13", "3.14"]
78
+
79
+ steps:
80
+ - uses: actions/checkout@v4
81
+
82
+ - name: Set up Python ${{ matrix.python-version }}
83
+ uses: actions/setup-python@v5
84
+ with:
85
+ python-version: ${{ matrix.python-version }}
86
+
87
+ - name: Install uv
88
+ uses: astral-sh/setup-uv@v5
89
+ with:
90
+ version: "latest"
91
+
92
+ - name: Create venv and install dependencies
93
+ run: |
94
+ uv venv
95
+ uv pip install -e ".[dev]"
96
+
97
+ - name: Run tests
98
+ run: uv run pytest tests/ -v --tb=short
99
+
100
+ - name: Coverage report
101
+ run: uv run pytest tests/ --cov=audnet --cov-report=term-missing --cov-fail-under=90
@@ -0,0 +1,74 @@
1
+ name: Auto Label Issues
2
+
3
+ on:
4
+ issues:
5
+ types: [opened, edited]
6
+
7
+ jobs:
8
+ label:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ issues: write
12
+ steps:
13
+ - uses: actions/github-script@v7
14
+ with:
15
+ script: |
16
+ const issue = context.payload.issue;
17
+ const title = issue.title.toLowerCase();
18
+ const body = (issue.body || '').toLowerCase();
19
+ let labelsToAdd = [];
20
+
21
+ // Keyword-based labeling (production pattern)
22
+ if (title.includes('fix') || title.includes('bug') || body.includes('bug')) {
23
+ labelsToAdd.push('bug');
24
+ }
25
+ if (title.includes('feat') || title.includes('enhance') || body.includes('feature')) {
26
+ labelsToAdd.push('enhancement');
27
+ }
28
+ if (title.includes('docs') || title.includes('documentation') || body.includes('readme')) {
29
+ labelsToAdd.push('documentation');
30
+ }
31
+ if (title.includes('security') || body.includes('secret') || body.includes('password')) {
32
+ labelsToAdd.push('security');
33
+ }
34
+ if (title.includes('performance') || title.includes('slow')) {
35
+ labelsToAdd.push('performance');
36
+ }
37
+ if (title.includes('refactor') || title.includes('clean')) {
38
+ labelsToAdd.push('refactor');
39
+ }
40
+ if (title.includes('chore') || title.includes('ci') || title.includes('test')) {
41
+ labelsToAdd.push('chore');
42
+ }
43
+
44
+ // Remove duplicates
45
+ labelsToAdd = [...new Set(labelsToAdd)];
46
+
47
+ if (labelsToAdd.length > 0) {
48
+ // Ensure each label exists (create if missing) - robust production fix
49
+ for (const label of labelsToAdd) {
50
+ try {
51
+ await github.rest.issues.getLabel({
52
+ owner: context.repo.owner,
53
+ repo: context.repo.repo,
54
+ name: label
55
+ });
56
+ } catch (error) {
57
+ if (error.status === 404) {
58
+ await github.rest.issues.createLabel({
59
+ owner: context.repo.owner,
60
+ repo: context.repo.repo,
61
+ name: label,
62
+ color: '0366d6' // GitHub blue
63
+ });
64
+ }
65
+ }
66
+ }
67
+
68
+ await github.rest.issues.addLabels({
69
+ owner: context.repo.owner,
70
+ repo: context.repo.repo,
71
+ issue_number: issue.number,
72
+ labels: labelsToAdd
73
+ });
74
+ }
@@ -0,0 +1,21 @@
1
+ name: Auto Labeler
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened]
6
+
7
+ jobs:
8
+ label:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ pull-requests: write
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Label PR based on changed files
17
+ uses: actions/labeler@v5
18
+ with:
19
+ repo-token: "${{ secrets.GITHUB_TOKEN }}"
20
+ configuration-path: .github/labeler.yml
21
+ sync-labels: true
@@ -0,0 +1,68 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*']
6
+
7
+ permissions:
8
+ contents: write # needed for creating GitHub Releases
9
+
10
+ jobs:
11
+ build-and-publish:
12
+ runs-on: ubuntu-latest
13
+ environment:
14
+ name: pypi
15
+ url: https://pypi.org/p/audnet
16
+ permissions:
17
+ id-token: write # required for PyPI Trusted Publishing (OIDC)
18
+
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ with:
22
+ fetch-depth: 0 # required for hatch-vcs to determine version from tags
23
+
24
+ - name: Set up Python
25
+ uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.14"
28
+
29
+ - name: Install uv
30
+ uses: astral-sh/setup-uv@v5
31
+ with:
32
+ version: "latest"
33
+
34
+ - name: Build wheel and sdist
35
+ run: uv build
36
+
37
+ - name: Publish to PyPI
38
+ uses: pypa/gh-action-pypi-publish@release/v1
39
+
40
+ - name: Extract changelog section for this version
41
+ id: changelog
42
+ run: |
43
+ VERSION="${GITHUB_REF_NAME#v}"
44
+ # Extract the section for this version from CHANGELOG.md
45
+ NOTES=$(python3 -c "
46
+ import re, sys
47
+ version = '${VERSION}'
48
+ with open('CHANGELOG.md') as f:
49
+ content = f.read()
50
+ # Find the section for this version
51
+ pattern = rf'## \[{re.escape(version)}\].*?(?=\n## \[|\Z)'
52
+ match = re.search(pattern, content, re.DOTALL)
53
+ if match:
54
+ print(match.group(0).strip())
55
+ else:
56
+ print(f'Version {version} release.')
57
+ ")
58
+ echo "notes<<EOF" >> "$GITHUB_OUTPUT"
59
+ echo "$NOTES" >> "$GITHUB_OUTPUT"
60
+ echo "EOF" >> "$GITHUB_OUTPUT"
61
+
62
+ - name: Create GitHub Release
63
+ uses: softprops/action-gh-release@v2
64
+ with:
65
+ body: ${{ steps.changelog.outputs.notes }}
66
+ files: dist/*
67
+ draft: false
68
+ prerelease: false
@@ -0,0 +1,26 @@
1
+ name: Size Label
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened]
6
+
7
+ jobs:
8
+ size-label:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ pull-requests: write
13
+ issues: write
14
+ steps:
15
+ - uses: pascalgn/size-label-action@v0.5.5
16
+ env:
17
+ GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
18
+ with:
19
+ sizes: >
20
+ {
21
+ "size/XS": 0,
22
+ "size/S": 10,
23
+ "size/M": 50,
24
+ "size/L": 200,
25
+ "size/XL": 500
26
+ }
@@ -0,0 +1,27 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ .eggs/
7
+ dist/
8
+ build/
9
+ *.egg
10
+
11
+ # Virtual environments
12
+ .venv/
13
+
14
+ # Testing
15
+ .pytest_cache/
16
+ .coverage
17
+ htmlcov/
18
+
19
+ # Generated reports (created by running audnet)
20
+ audit_report.*
21
+
22
+ # User data — never commit real device inventories or baselines with credentials
23
+ inventories/
24
+ baselines/
25
+
26
+ # Agent working files
27
+ .hermes/
@@ -0,0 +1,36 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-toml
9
+ - id: check-added-large-files
10
+ args: ['--maxkb=500']
11
+ - id: debug-statements
12
+ - id: mixed-line-ending
13
+
14
+ - repo: https://github.com/astral-sh/ruff-pre-commit
15
+ rev: v0.11.7
16
+ hooks:
17
+ - id: ruff
18
+ args: [--fix]
19
+ - id: ruff-format
20
+
21
+ - repo: https://github.com/pre-commit/mirrors-mypy
22
+ rev: v1.15.0
23
+ hooks:
24
+ - id: mypy
25
+ pass_filenames: false
26
+ args: [src/]
27
+ additional_dependencies:
28
+ - netmiko>=4.3.0
29
+ - textfsm>=1.1.0
30
+ - jinja2>=3.1.0
31
+ - pyyaml>=6.0
32
+ - pydantic>=2.0
33
+ - typer>=0.12.0
34
+ - rich>=13.0
35
+ - structlog>=24.0
36
+ - tenacity>=8.5.0
@@ -0,0 +1,86 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-06-11
9
+
10
+ ### Added
11
+
12
+ - Initial release: network security & compliance auditor with SSH-based device collection
13
+ - Parallel SSH collector (`collector.py`) using Netmiko + ThreadPoolExecutor with configurable concurrency
14
+ - Async collector prototype (`collector_async.py`) using asyncio + asyncssh for large-scale performance
15
+ - TextFSM parser with vendor-aware template selection for structured CLI output
16
+ - Pattern-based compliance engine with 4 security rules (SSH, NTP, syslog, interface) and per-vendor overrides
17
+ - Vendor registry/dispatch pattern (`VendorProfile`, `register_vendor()`) for multi-vendor support
18
+ - Jinja2 report generator producing Markdown and HTML audit reports
19
+ - Typer CLI with `--device`, `--check`, `--json`, `--dry-run`, `--strict`, `--verbose`, `--version` flags
20
+ - YAML inventory loader with environment variable resolution for credential management
21
+ - Pydantic models for baseline schema validation
22
+ - Structured exception hierarchy with retry logic for transient SSH failures
23
+ - SSH key-based authentication support
24
+ - `structlog` with secret redaction for safe logging
25
+ - Plaintext password detection with `--strict` mode for CI/CD pipelines
26
+ - Pre-commit hooks: ruff, mypy strict, bandit, and formatting checks
27
+ - GitHub Actions CI: lint, security scan (bandit + pip-audit), and multi-version testing (3.12/3.13/3.14)
28
+ - GitHub Actions auto-close workflow for linked issues on PR merge
29
+ - Comprehensive test suite: 208 tests, 98.61% coverage
30
+ - Sample device inventory and security baseline configuration
31
+ - Documentation: README with usage examples, SECURITY.md, CONTRIBUTING.md, CHANGELOG.md
32
+
33
+ ### Security
34
+
35
+ - Passwords stored as `SecretStr` (Pydantic), never rendered in logs or output
36
+ - Structlog processor redacts sensitive keys (`password`, `key_file`, `secret`, `passwd`, `token`)
37
+ - `--strict` mode enforces env-var-only passwords in CI/CD pipelines
38
+
39
+ ## [0.1.2] - 2026-06-12
40
+
41
+ ### Changed
42
+
43
+ - Renamed package from `net-audit` / `net_audit` to `audnet` everywhere: Python package, CLI entry point, PyPI project name, env vars (`AUDNET_PASSWORD`), docs, badges (#108, #109)
44
+ - Renamed GitHub repository from `islam666/net-audit` to `islam666/Audnet`
45
+
46
+ ### Fixed
47
+
48
+ - CI badge URL updated to match renamed repo
49
+ - Release badge URL updated to match renamed repo
50
+ - Labeler config paths updated from `src/net_audit/` to `src/audnet/`
51
+ - Sample SSH key filename in inventory updated to `audnet_id_ed25519`
52
+
53
+ ## [Unreleased]
54
+
55
+ ## [0.1.1] - 2026-06-12
56
+
57
+ ### Added
58
+
59
+ - `--async` flag for asyncio-based collector (asyncssh) — recommended for >20 devices (#88)
60
+ - `--no-fail` flag to exit with code 0 even when compliance checks fail (default: exit code 1 on failures) (#78)
61
+ - Hostname parsing from `show version` output (#73)
62
+ - Serial number parsing from `show version` output (#72)
63
+ - PyPI publish workflow — automated build and publish to PyPI on `v*` tags via Trusted Publishing (OIDC) (#90)
64
+ - PyPI version and Python version badges in README (#90)
65
+
66
+ ### Fixed
67
+
68
+ - Strict mode now also checks `secret`, `passwd`, and `token` fields for plaintext passwords (#76)
69
+ - `CheckConfig` model missing `vendor_patterns` field — added with proper validation (#85)
70
+ - SSH host key verification not configurable in async collector (#84)
71
+ - Invalid device entries in inventory no longer abort entire load — skipped with warning (#83)
72
+ - Device order not preserved in `collect_all` results — now maintains insertion order (#82)
73
+ - Missing vendor TextFSM templates silently returned empty results — now raises `ParseError` (#81)
74
+ - `connect_timeout` passed as string instead of int in async collector (#79)
75
+ - Report templates not lazy-loaded — now loaded on demand with proper error handling (#77)
76
+ - Extra TextFSM fields in `ParsedVersion` caused validation errors — now ignored (#75)
77
+ - Baseline `check_name` not propagated to `ComplianceResult` — now correctly set (#74)
78
+ - `_check_no_open_ports` backward walk logic replaced with forward-scan interface tracking for correctness (#87)
79
+ - Magic slot indices in parser replaced with `Slot` enum for type safety (#86)
80
+
81
+ ### Documentation
82
+
83
+ - Added quick-install section to README (#53)
84
+ - Added quick start guide (#80)
85
+ - Added GitHub Release badge to README (#51)
86
+ - Updated CONTRIBUTING.md release process to reflect automated PyPI publish (#90)