Files
shopdb-flask/plugins/geenforce/frontend/views/GeEnforceSettings.vue
cproudlock 035419fa51 ADR-015: stop shipping one site's values, and make the rule a gate
The scanner has been reporting the same count for weeks, which is what a rule
that only prints becomes. It now FAILS the build, and it looks where the leaks
actually were: PowerShell, the installer, the seeds, generated JSON, the
frontend - case-insensitively, across plugins, shopdb, scripts, deploy, tools.
A line that is deliberate declares itself with an ADR-015-OK marker and a
reason, so the claim is visible in review instead of tolerated in silence.

What it found, fixed here:

- The shadow client wrote one site's ShopDB URL into HKLM whenever the registry
  disagreed. At the site it was written for that reads as healing drift;
  anywhere else it overwrites the site's own address on every enforce cycle,
  and the site cannot win because the cycle repeats. The bay's value now wins,
  an explicit -BaseUrl seeds it, and with neither there is nothing honest to
  write, so it says so and skips.
- The kiosk dispatcher fell back to one plant's host when HKLM was unset, so a
  kiosk elsewhere quietly opened a server it has no business reaching. The
  fallback is now this site's site_base_url, baked in at seed time, and the
  dispatcher refuses rather than guessing when neither is set. Its legacy
  shortcut matcher derives the host from that URL instead of naming one.
- The OpenAPI generator hardcoded a production hostname into every spec it
  generated, which then published to a public wiki. The relative mount is the
  only server it can honestly name; a site passes its own by environment.
- Placeholders and examples in the UI and the client help offered real internal
  subnets and a real production URL. They now use documentation ranges.

Both publication gates - the export scrub and the docs publishability test -
carry the site patterns, which neither did. One plant's hostname, FQDN and
internal networks are out of the documentation and the generated specs.

Comments naming the reference site are reworded rather than deleted: the
reasoning is worth keeping, the plant name is not what makes it true.
2026-08-14 13:47:39 -04:00

110 lines
3.2 KiB
Vue

<template>
<div class="geenforce-settings">
<h2>Client Access</h2>
<p class="muted">
Fleet PCs on a trusted network can reach the GE-Enforce client endpoints
(manifest, payload, report) without a per-PC token. List the trusted
networks below. Leave it empty to require a <code>geenforce.fetch</code>
token instead.
</p>
<label class="field-label" for="cidrs">Allowed networks (CIDR)</label>
<textarea
id="cidrs"
v-model="cidrs"
class="cidr-input"
rows="5"
spellcheck="false"
placeholder="192.0.2.0/24&#10;198.51.100.0/26"
></textarea>
<small class="input-hint">
One CIDR (or a plain IP) per line, or comma-separated. Example:
<code>192.0.2.0/24, 198.51.100.0/26</code>. A caller from any of these
networks may pull manifests + payloads and post reports with no token.
</small>
<div class="actions">
<button class="btn btn-primary" :disabled="saving" @click="save">
{{ saving ? 'Saving...' : 'Save' }}
</button>
<span v-if="message" :class="['status', ok ? 'ok' : 'err']">{{ message }}</span>
</div>
<div class="note">
<strong>Note:</strong> this is network-perimeter trust. Any host on a
listed network is trusted - including a compromised one. It does not
replace per-device identity if you need tamper-proof report integrity.
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import api from '@/api'
const cidrs = ref('')
const saving = ref(false)
const message = ref('')
const ok = ref(false)
function unwrap(response) { return response.data.data }
async function load() {
try {
const config = unwrap(await api.get('/geenforce/config'))
cidrs.value = config.allowedcidrs || ''
} catch (e) {
message.value = 'Could not load config.'
ok.value = false
}
}
async function save() {
saving.value = true
message.value = ''
try {
const config = unwrap(await api.put('/geenforce/config', { allowedcidrs: cidrs.value }))
cidrs.value = config.allowedcidrs || '' // server returns the normalized list
ok.value = true
message.value = 'Saved.'
} catch (e) {
ok.value = false
message.value = e?.response?.data?.data?.error?.message || 'Save failed.'
} finally {
saving.value = false
}
}
onMounted(load)
</script>
<style scoped>
.geenforce-settings { max-width: 640px; }
.muted { color: var(--text-light); }
.field-label { display: block; font-weight: 600; margin: 1rem 0 0.35rem; }
.cidr-input {
width: 100%;
font-family: 'SF Mono', 'Consolas', monospace;
font-size: 0.9rem;
padding: 0.6rem 0.7rem;
border: 1px solid var(--border);
border-radius: 0.25rem;
background: var(--bg-card-solid);
color: var(--text);
resize: vertical;
}
.input-hint { display: block; color: var(--text-light); margin-top: 0.35rem; }
.actions { display: flex; align-items: center; gap: 0.75rem; margin-top: 1rem; }
.status.ok { color: var(--success); font-weight: 600; }
.status.err { color: var(--danger); font-weight: 600; }
.note {
margin-top: 1.5rem;
padding: 0.75rem 1rem;
border-left: 4px solid var(--warning);
background: var(--bg-card);
border-radius: 0.25rem;
color: var(--text-light);
font-size: 0.9rem;
}
</style>