Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,27 @@ jobs:
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
path: catalog

- name: Check out DOCSight manifest contract
uses: actions/checkout@v4
with:
repository: itsDNNS/docsight
path: docsight-core

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Validate manifests against DOCSight core
run: python3 docsight-core/app/manifest_contract.py catalog

- name: Run registry validation
working-directory: catalog
run: python3 scripts/validate_registry.py

- name: Run validation tests
run: python3 -m unittest tests/test_validate_registry.py
working-directory: catalog
run: python3 -m unittest discover -s tests -q
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,13 @@ Declare default values and they are automatically registered in DOCSight's confi

Use `config_secrets` for module passwords, API tokens, and other sensitive settings:

- Every key in `config_secrets` must also exist in the same manifest's `config` object.
- Every key in `config_secrets` must also exist in the same manifest's `config` object with a string default, normally `""`; non-string defaults are rejected so encrypted values never enter scalar coercion.
- `config_secrets` must be a list of unique strings.
- Secret keys are encrypted at rest and masked in Settings after they are saved.
- Community collectors can read only their own declared secret keys; they still cannot read DOCSight core secrets such as modem passwords or global API tokens.
- Settings templates should render secret fields as empty password/token inputs. Do not write saved secret values back into HTML `value` attributes.
- Settings templates should render secret fields as empty password/token inputs with `data-config-secret="true"`. Add `data-saved-secret="true"` only when the masked config value indicates an existing saved secret. This explicit metadata makes an untouched field post the mask while an edited field posts the new value. Do not write saved secret values back into HTML `value` attributes.

The manifest capability contract is owned by DOCSight core rather than duplicated in this catalog. With sibling checkouts, run `python3 ../docsight/app/manifest_contract.py .`; catalog CI performs the same check against current core before running the registry-specific validator.

---

Expand Down Expand Up @@ -534,7 +537,7 @@ Modules reviewed and tested by the DOCSight team receive `"verified": true`. Unv
Before opening your PR, verify:

- [ ] **Auth on all routes** — every `@bp.route` must have `@require_auth` (import from `app.web`)
- [ ] **No credentials in HTML** — password/token fields must use `value=""` with a `(saved)` placeholder, never `value="{{ config.password }}"`
- [ ] **No credentials in HTML** — password/token fields must use `value=""`, `data-config-secret="true"`, and conditional `data-saved-secret="true"` metadata, never `value="{{ config.password }}"`
- [ ] **Escape dynamic content** — any value from API responses inserted via `innerHTML` must be HTML-escaped; use `textContent` where possible
- [ ] **English API errors** — error messages returned by API endpoints must be English (UI translations go in i18n files)
- [ ] **No exception details in responses** — use generic error messages ("Connection failed"), log details server-side with `logger.exception()`
Expand Down
2 changes: 1 addition & 1 deletion TEMPLATE-THRESHOLDS/manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"id": "community.thresholds_CHANGEME",
"id": "community.thresholds_changeme",
"name": "CHANGEME Thresholds",
"description": "Signal thresholds for CHANGEME cable networks",
"version": "1.0.0",
Expand Down
10 changes: 7 additions & 3 deletions TEMPLATE/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ docker restart docsight
## Configuration

List any configuration keys here. If the module stores passwords or API tokens,
declare those keys in `config_secrets` in `manifest.json` and render the field as
an empty password/token input in Settings. Saved secret values must not be written
back into HTML `value` attributes.
declare those keys in `config_secrets` in `manifest.json` with string defaults,
normally `""`, and render the field as an empty password/token input in Settings
with `data-config-secret="true"`. Non-string secret defaults are invalid because
encrypted values must not enter boolean or integer coercion. When the masked
config value indicates an existing secret, also render
`data-saved-secret="true"`. Saved secret values must not be written back into
HTML `value` attributes.

## API Endpoints

Expand Down
2 changes: 1 addition & 1 deletion registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"description": "Monitor WAN1 and WAN2 on Ubiquiti UDM Pro/SE devices. Tracks link state, alive status, and failover events in the global DOCSight event log.",
"author": "Oggy512",
"repo": "https://github.com/itsDNNS/docsight-modules",
"version": "3.1.2",
"version": "3.1.3",
"min_app_version": "2026.2",
"type": "integration",
"verified": true,
Expand Down
12 changes: 12 additions & 0 deletions tests/test_validate_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ def make_valid_fixture(


class ValidateRegistryTests(unittest.TestCase):
def test_udm_password_field_uses_explicit_saved_secret_metadata(self):
root = Path(__file__).resolve().parents[1]
template = (
root / "udm-wan-monitor" / "templates" / "udm_wan_settings.html"
).read_text(encoding="utf-8")

self.assertIn('name="udm_wan_password"', template)
self.assertIn('value=""', template)
self.assertIn('data-config-secret="true"', template)
self.assertIn('data-saved-secret="true"', template)
self.assertNotIn('value="{{ config.udm_wan_password', template)

def test_valid_registry_accepts_installable_module_directory(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
Expand Down
2 changes: 1 addition & 1 deletion udm-wan-monitor/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": "community.udm_wan_monitor",
"name": "UDM WAN-Monitor",
"description": "Überwacht WAN1 und WAN2 einer Ubiquiti UDM Pro und erzeugt Events im globalen DOCSight-Log.",
"version": "3.1.2",
"version": "3.1.3",
"author": "Oggy512",
"minAppVersion": "2026.2",
"type": "integration",
Expand Down
3 changes: 2 additions & 1 deletion udm-wan-monitor/templates/udm_wan_settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@
{{ t.get('community.udm_wan_monitor.password', 'Passwort') }}
</label>
<input class="form-input" type="password" id="udm_wan_password" name="udm_wan_password"
value="" placeholder="{% if config.udm_wan_password %}(saved){% else %}••••••••{% endif %}"
value="" placeholder="{% if config.udm_wan_password %}{{ t.get('saved_ph', 'Saved') }}{% else %}••••••••{% endif %}"
data-config-secret="true"{% if config.udm_wan_password %} data-saved-secret="true"{% endif %}
autocomplete="new-password">
<span class="form-hint">
{{ t.get('community.udm_wan_monitor.password_hint', 'Wird verschlüsselt in der DOCSight-Konfiguration gespeichert.') }}
Expand Down
Loading