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
23 changes: 23 additions & 0 deletions scripts/validate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,25 @@ export function checkTool({ data, file }, reg) {
return errs;
}

// The coverage map and the controls directory must agree: an entry marked
// 'existing' has a control file, and every control file is listed in the map.
export function checkCoverage(coverageMap, controlIds) {
const errs = [];
const mapped = new Set();
for (const p of coverageMap) {
for (const c of p.controls ?? []) {
mapped.add(c.id);
const has = controlIds.has(c.id);
if (c.status === 'existing' && !has) errs.push(`coverage-map: ${c.id} is 'existing' but has no control file`);
if (c.status !== 'existing' && has) errs.push(`coverage-map: ${c.id} is '${c.status}' but a control file exists`);
}
}
for (const id of controlIds) {
if (!mapped.has(id)) errs.push(`coverage-map: control ${id} is not listed`);
}
return errs;
}

function idsFromDir(dir, field) {
const out = new Set();
for (const f of readdirSync(dir).filter((f) => f.endsWith('.md'))) {
Expand All @@ -88,10 +107,14 @@ export async function runValidation() {
const dir = 'src/content/controls';
const files = readdirSync(dir).filter((f) => f.endsWith('.md') && !f.startsWith('_'));
const all = [];
const controlIds = new Set();
for (const f of files) {
const { data, content } = matter(readFileSync(join(dir, f), 'utf8'));
controlIds.add(data.id);
all.push(...checkControl({ data, body: content, file: f }, reg));
}
const { coverageMap } = await import('../src/data/coverage-map.js');
all.push(...checkCoverage(coverageMap, controlIds));

// Toolchain integrity: every referenced tool slug must exist, and every
// hardware tool's `software` slugs must exist.
Expand Down
13 changes: 12 additions & 1 deletion scripts/validate.test.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { checkControl, checkTool } from './validate.mjs';
import { checkControl, checkTool, checkCoverage } from './validate.mjs';

const registries = {
bsamKeys: new Set(['BSAM-EN-01']),
Expand Down Expand Up @@ -92,3 +92,14 @@ test('a successor must resolve and is only accepted with eol or stale', () => {
test('lifecycle fields without a status are rejected', () => {
assert.match(checkTool(tool({ statusNote: 'x' }), registries)[0], /status is not/);
});

test('coverage map and control files must agree', () => {
const map = [{ id: 'BLE', controls: [
{ id: 'RFSAM-BLE-SP-01', status: 'existing' },
{ id: 'RFSAM-BLE-AT-02', status: 'planned' },
] }];
assert.deepEqual(checkCoverage(map, new Set(['RFSAM-BLE-SP-01'])), []);
assert.match(checkCoverage(map, new Set())[0], /'existing' but has no control file/);
assert.match(checkCoverage(map, new Set(['RFSAM-BLE-SP-01', 'RFSAM-BLE-AT-02']))[0], /a control file exists/);
assert.match(checkCoverage(map, new Set(['RFSAM-BLE-SP-01', 'RFSAM-BLE-LL-09']))[0], /is not listed/);
});
2 changes: 1 addition & 1 deletion src/content/resources/rfsam-res-11.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
id: RFSAM-RES-11
title: Wi-Fi monitor-mode capture and survey
---
Put a monitor-mode-capable adapter (e.g. ALFA AWUS036ACH / RTL8812AU, or ESP32-C6 Minino) into monitor mode, channel-hop across 2.4/5/6 GHz, and capture all 802.11 frames. Confirm injection support with aireplay-ng --test before relying on active controls. Add GPS for wardriving/geolocation of APs.
Put a monitor-mode-capable adapter (e.g. ALFA AWUS036ACH / RTL8812AU, or ESP32-C6 Minino) into monitor mode, channel-hop across 2.4/5/6 GHz, and capture all 802.11 frames. Confirm injection support with `aireplay-ng --test` before relying on active controls. Add GPS for wardriving/geolocation of APs.
2 changes: 1 addition & 1 deletion src/data/coverage-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const coverageMap = [
{ protocol: 'ADSB', controls: [
{ id: 'RFSAM-ADSB-PHY-01', title: 'Message capture and decode', layer: 'PHY', status: 'existing', objective: 'Assess capture and decoding of ADS-B messages.' },
{ id: 'RFSAM-ADSB-LL-01', title: 'Message authenticity assessment', layer: 'LL', status: 'existing', objective: 'Assess what authenticity guarantees, if any, the link provides.' },
{ id: 'RFSAM-ADSB-AT-01', title: 'Forge and inject (contained lab)', layer: 'AT', status: 'existing', objective: 'Assess resilience to forged/injected ADS-B frames (authorised, contained-lab testing only).' },
{ id: 'RFSAM-ADSB-AT-01', title: 'Forge and inject (contained lab)', layer: 'AT', status: 'planned', objective: 'Assess resilience to forged/injected ADS-B frames (authorised, contained-lab testing only).' },
]},
{ protocol: 'NR5G', controls: [
{ id: 'RFSAM-NR5G-SP-01', title: 'Cell identification and capture', layer: 'SP', status: 'existing', objective: 'Assess identification and capture of the target 5G NR cell.' },
Expand Down
Loading