@@ -85,6 +85,75 @@ details[open] {
8585 text-align: left;
8686}
8787</style>
88+ <script type="module">
89+ /** Takes a threat model table header, which should be written in Bikeshed
90+ * like
91+ *
92+ * <th><div><div> [[#goal-transfer-userid]] </div></div></th>
93+ *
94+ * and extracts the "transfer-userid" part from the generated <a> tag.
95+ */
96+ function findGoalFromColumnHeader(header) {
97+ const firstDiv = header.firstElementChild;
98+ const secondDiv = firstDiv?.firstElementChild;
99+ const link = secondDiv?.firstElementChild;
100+ if (header.colSpan != 1 ||
101+ link == null ||
102+ firstDiv.tagName !== "DIV" ||
103+ secondDiv.tagName !== "DIV" ||
104+ link.tagName !== "A" ||
105+ link.href === "") {
106+ console.error("Expected header cell to be written like <th><div><div><a href=...>...\n",
107+ header);
108+ return "error";
109+ }
110+ const target = new URL(link.href);
111+ if (!target.hash.startsWith("#goal-")) {
112+ console.error("Column header expected to have a fragment starting with '#goal-': ",
113+ link);
114+ }
115+ return target.hash.slice(6);
116+ }
117+
118+ for (const table of document.getElementsByClassName("threatmodel")) {
119+ // Make sure that the checks and Xes in the source are meant to apply to the
120+ // attacker goal described by the heading they appear under.
121+ const goalOrder = [];
122+ let goalsStarted = false;
123+ for (const header of table.rows[0].cells) {
124+ if (header.textContent.trim() === "" && !goalsStarted) {
125+ for (const _ of Array(header.colSpan)) {
126+ goalOrder.push(undefined);
127+ }
128+ continue;
129+ }
130+ goalsStarted = true;
131+ goalOrder.push(findGoalFromColumnHeader(header));
132+ }
133+
134+ // Check the body rows.
135+ for (const row of Array.prototype.slice.call(table.rows, 1)) {
136+ let column = 0;
137+ for (const cell of row.cells) {
138+ let expectedGoal = goalOrder[column];
139+ column += cell.colSpan;
140+ if (expectedGoal === undefined && "goal" in cell.dataset) {
141+ console.error("Cell without visible header has a data-goal attribute.\n",
142+ cell);
143+ } else if (expectedGoal === "error") {
144+ // Don't be spammy if the header's broken.
145+ continue;
146+ } else if (cell.dataset.goal !== expectedGoal) {
147+ console.error("Cell expected to have goal '" + expectedGoal +
148+ "' but actually claims '" + cell.dataset.goal + "'.\n",
149+ cell);
150+ } else {
151+ // The cell is in the right column.
152+ }
153+ }
154+ }
155+ }
156+ </script>
88157
89158Advisement: This document is at a very early stage. Many things in it are wrong
90159and/or incomplete. Please take it as a rough shape for how we might document the
0 commit comments