---
title: 'Hardening Principles'
source: 'https://academia.sh/en/courses/endpoint-security/hardening-principles'
course: 'Endpoint and System Hardening'
language: en
updated: '2026-09-26T03:02:51+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Identifies automatically run item types as foothold points and separates them from installed packages'
    - 'Counts the closed point and the merely recorded point separately in the default install inventory'
    - 'Finds the item that diverges by machine class and calculates the legitimate-work cost of removing it'
---

# Hardening Principles

The course sets its unit of measure: the 84 foothold points a default install leaves across four machine classes are counted, clearing unnecessary items closes 40 of them, 44 are only written to a baseline, and the legitimate-work cost of closing them comes out to 41 manual steps.

The previous course tied identity to a root: who a key belongs to now rests on a named chain. Even
if the entire chain is built correctly, that root sits on top of a machine, and if the machine is
compromised the chain keeps working in the attacker's hands.

This course starts from there, and it opens with an assumption different from the previous four
courses: **code has already run once on the endpoint**. What gets counted is no longer whether entry
is blocked, but from how many points persistence is possible after entry. Code that has run once is
a one-time event if it does not run again by itself when the machine restarts or the user logs on;
for it to run again, an item has to be added to the machine's own automatic-run mechanism. Every item
in that mechanism is a **foothold point**, and it can be counted. This lesson sets the course's unit
of measure.

## Foothold Point and Installed Item

An inventory item is a foothold point if nobody has to do anything for it to run. Five types meet
this definition: a **boot entry**, a **service definition**, a **scheduled task**, a **logon
script**, and a **configuration hook**. What they share is not their form but their function — each
carries a **definition**, and whoever can write that definition makes their own chosen thing run the
next time the machine boots or the next time a session opens.

An installed package does not meet this definition. A binary sitting on disk does not run by itself;
for it to run, either a user has to invoke it or one of the five types above has to name it. The
distinction narrows and sharpens the measurement: not the whole inventory, only the automatically run
part of it is counted. In this course, **how** a foothold point is defined — which file gets what
written to it — is never written down anywhere; what is counted is that the type exists, and how
many there are.

The measurement is set up across the four machine classes of the **regional measurement network**:
field team laptops, branch endpoints, meter collector machines, and central processing servers.

- **SH1.** The thirty-three-item inventory and the four classes are a **model**; no real machine is
  scanned, no real removal is performed.
- **SH2.** Only the five automatic types count toward the foothold-point tally; installed packages
  are counted separately.
- **SH3.** The "required" column is treated as derived from a job description and is fixed in the
  model. In reality this column is filled in by an inventory pass and changes as roles change.
- **SH4.** Removal cost is the number of legitimate tasks that fall to a manual step when the item is
  removed; it was written in by hand.
- **SH5.** Removal is total: an item either exists or it does not. The distinction between disabling
  and removing is not modeled; the latter also deletes the definition, the former leaves it in place.
- **SH6.** The field for who can write the definition carries a single value and shows the broadest
  privilege.

```js
// harden/inventory.mjs — the item inventory a default install leaves behind IS A MODEL.
// No real machine is scanned, no real removal is performed; the numbers derive from this file.
export const CLASS = ['field-laptop', 'branch-endpoint', 'collector', 'processing-server'];
// Five automatically run item types count as a foothold point; 'package' does not.
export const POINT_TYPE = ['boot', 'service', 'task', 'logon', 'hook'];
// row: name | type | installed mask | required mask | who can write the definition | removal cost
// mask is four characters, in CLASS order; 'k' present, '.' absent.
// cost: the number of legitimate tasks that fall to a manual step when the item is removed.
export const ITEM = `
  boot-manager-entry           boot    kkkk kkkk administrator       0
  recovery-boot-entry          boot    kkkk .... administrator       2
  network-boot                 boot    .kkk ..k. administrator       0
  remote-management-service    service kkkk .kkk administrator       0
  print-spooler                service kk.. .k.. administrator       0
  discovery-broadcast          service kkkk .... administrator       1
  remote-desktop                service kk.. .k.. administrator       0
  metrics-collector-agent      service kkkk kkkk administrator       0
  log-forwarder-agent          service kkkk kkkk administrator       0
  patch-agent                   service kkkk kkkk administrator       0
  helper-tool-service           service kkkk .... local-administrator 0
  cloud-sync-service            service k... .... user                2
  meter-reading-service         service ..k. ..k. administrator       0
  job-queue-service             service ...k ...k administrator       0
  update-check                  task    kkkk kkkk administrator       0
  disk-maintenance               task    kkkk kkkk administrator       0
  telemetry-upload               task    kkkk .... administrator       0
  helper-tool-update             task    kkkk .... local-administrator 1
  user-task-folder                task    kk.. .... user                3
  backup-trigger                  task    ..kk ..kk administrator       0
  logon-script                    logon   kk.. kk.. administrator       0
  user-startup-folder             logon   kk.. .... user                3
  shell-config-file               logon   kkkk k.k. user                2
  package-manager-hook            hook    kkkk ...k administrator       1
  driver-load-hook                 hook    kkkk kk.. administrator       0
  auth-plugin                      hook    kkkk .kkk administrator       0
  shell-plugin-directory           hook    kk.. .... user                2
  document-editor                  package kk.. kk.. -                   0
  archive-tool                     package kkkk kkkk -                   0
  script-runtime                   package kkkk ..kk -                   1
  database-client                  package ..kk ..kk -                   0
  diagnostic-toolkit               package kkkk .... -                   3
  field-measurement-app            package k... k... -                   0`
  .trim().split('\n').map((r) => {
    const [name, type, installed, required, writer, cost] = r.trim().split(/\s+/);
    return { name, type, installed, required, writer, cost: Number(cost),
      point: POINT_TYPE.includes(type) };
  });

export const isInstalled = (k, i) => k.installed[i] === 'k';
export const isRequired = (k, i) => k.required[i] === 'k';
// Items installed in a class, not required, and therefore removable.
export const surplus = (i) => ITEM.filter((k) => isInstalled(k, i) && !isRequired(k, i));
export const points = (i) => ITEM.filter((k) => k.point && isInstalled(k, i));
export const remaining = (i) => points(i).filter((k) => isRequired(k, i));
export const measure = (i) => ({
  installed: ITEM.filter((k) => isInstalled(k, i)).length,
  point: points(i).length,
  closed: surplus(i).filter((k) => k.point).length,
  remaining: remaining(i).length,
  cost: surplus(i).filter((k) => k.point).reduce((a, k) => a + k.cost, 0),
});
```

## Counting the Inventory

```js
// harden/count.mjs — foothold points, closed points, and removal cost across four machine classes
import { CLASS, POINT_TYPE, ITEM, measure, isInstalled, isRequired, points, remaining, surplus } from './inventory.mjs';

const row = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join('').trimEnd());
const total = (f) => CLASS.reduce((a, _, i) => a + f(i), 0);

console.log(`${ITEM.length} inventory items; ${ITEM.filter((k) => k.point).length} of them ` +
  `automatically run (${POINT_TYPE.join(', ')})`);

const A = [-20, 11, 7, 9, 11, 8];
console.log('\n1. count per class');
row(A, 'class', 'installed', 'point', 'closed', 'remaining', 'cost');
for (const [i, s] of CLASS.entries()) {
  const o = measure(i);
  row(A, s, o.installed, o.point, o.closed, o.remaining, o.cost);
}
row(A, 'total', total((i) => measure(i).installed), total((i) => measure(i).point),
  total((i) => measure(i).closed), total((i) => measure(i).remaining), total((i) => measure(i).cost));

const B = [-8, 7, 9, 11, 8];
console.log('\n2. count per type, sum of four classes');
row(B, 'type', 'point', 'closed', 'remaining', 'cost');
for (const t of POINT_TYPE)
  row(B, t, total((i) => points(i).filter((k) => k.type === t).length),
    total((i) => surplus(i).filter((k) => k.point && k.type === t).length),
    total((i) => remaining(i).filter((k) => k.type === t).length),
    total((i) => surplus(i).filter((k) => k.point && k.type === t).reduce((a, k) => a + k.cost, 0)));

console.log('\n3. point diverging by class (required in one class, surplus in another)');
const diverging = ITEM.filter((k) => k.point && CLASS.some((_, i) => isInstalled(k, i) && isRequired(k, i))
  && CLASS.some((_, i) => isInstalled(k, i) && !isRequired(k, i)));
for (const k of diverging) row([-30, -9, -6], k.name, k.type, k.required);

const nonAdmin = (i) => points(i).filter((k) => k.writer !== 'administrator');
console.log(`\npoints needing a separate decision       ${diverging.length} items, ` +
  `${total((i) => diverging.filter((k) => isInstalled(k, i)).length)} decisions`);
console.log(`points definable without an administrator ${total((i) => nonAdmin(i).length)} decisions, ` +
  `closed ${total((i) => nonAdmin(i).filter((k) => !isRequired(k, i)).length)}, ` +
  `remaining ${total((i) => nonAdmin(i).filter((k) => isRequired(k, i)).length)}`);
console.log(`removable package                          ${total((i) => surplus(i).filter((k) => !k.point).length)} decisions, ` +
  `cost ${total((i) => surplus(i).filter((k) => !k.point).reduce((a, k) => a + k.cost, 0))}`);
console.log(`total point decisions ${total((i) => measure(i).point)}: ` +
  `closed ${total((i) => measure(i).closed)}, ` +
  `written to baseline only ${total((i) => measure(i).remaining)}`);
```

```
33 inventory items; 27 of them automatically run (boot, service, task, logon, hook)

1. count per class
class                 installed  point   closed  remaining    cost
field-laptop                 28     23       14          9      15
branch-endpoint              27     23       11         12      15
collector                    23     19        7         12       5
processing-server            23     19        8         11       6
total                       101     84       40         44      41

2. count per type, sum of four classes
type      point   closed  remaining    cost
boot         11        6          5       8
service      31       12         19       6
task         20       10         10      10
logon         8        4          4      10
hook         14        8          6       7

3. point diverging by class (required in one class, surplus in another)
network-boot                  boot     ..k.
remote-management-service     service  .kkk
print-spooler                 service  .k..
remote-desktop                service  .k..
shell-config-file             logon    k.k.
package-manager-hook          hook     ...k
driver-load-hook              hook     kk..
auth-plugin                   hook     .kkk

points needing a separate decision       8 items, 27 decisions
points definable without an administrator 19 decisions, closed 17, remaining 2
removable package                          6 decisions, cost 14
total point decisions 84: closed 40, written to baseline only 44
```

## The Closed Point and the Recorded Point

The total row gives the course's two columns at once. Across four classes there are 84 foothold-point
decisions; clearing unnecessary items **closes 40 of them** and **leaves 44 open**. The only thing
that can be done for the 44 open points is to write their definitions to a baseline and compare
later deviations against that line. This is not a closure; a recorded point still runs when it is
used, it is only noticed when the comparison is made.

Keeping the two columns separate is this course's rule. The sentence "it was hardened" does not by
itself say which column something was written to. A closed point cannot be used again; a recorded
point can be used, and it is noticed only when someone reads the record.

The model treats an item as either present or absent (SH5), and that collapses two separate
operations into a single number in reality. **Disabling** a service leaves its definition in place
and only flips the run flag off; **removing** it also deletes the definition. For counting purposes
the difference is this: a disabled item does not belong in the closed column, it belongs in the
recorded column, because whoever can write the definition can flip the flag back on. When the
difference is measured, the number 40 drops in most inventories — items that cannot be removed but
can be disabled get mistaken for closed. This lesson's count assumes removal actually happened; if
it did not happen at an endpoint, that item moves to the second column.

The class rows show a reversed distribution. The field laptop carries the most items (28 installed,
23 points) and is also the class cleaned the most: 14 points close, 9 remain. On the collector
machine, only 7 of 19 points close, because whatever is installed is nearly always required. So
cleanup pays off the most on a machine built for many purposes with no single job, and on a machine
with a single job the room to gain is already narrow.

The type table shows where the remaining side concentrates. Of thirty-one service definitions, only
12 close; 19 services remain, and that is the largest piece of the 44 remaining points. The metrics
collector, the log forwarder, the patch agent, and the auth plugin cannot be removed, because the
machine's manageability depends on them. Hardening's limit shows up here: the items that make an
endpoint manageable are also the ways to persist on that endpoint.

## Who Can Write It Matters More Than How Many There Are

A separate row flags 19 of the 84 points: points definable without administrator privilege. The user
task folder, the user startup folder, the shell plugin directory, the shell config file, the cloud
sync service, and two helper-tool items that ship with local-administrator privilege belong to this
group. Cleanup **closes 17 of them**, leaving **2**.

The smallness of the number does not shrink the importance of the result. A foothold point that
requires administrator privilege is an item usable by someone who has already obtained that
privilege; a point that does not require administrator privilege is open to code running in an
ordinary user session. The difference between the two items is not in the count, it is in which step
has to happen before it becomes usable. The two remaining items — the shell config file on the field
laptop and on the collector machine — cannot be removed, because in both classes the work itself
runs through the shell.

## Items That Diverge by Class

The third block separates eight items: each is required in one class, surplus in another. The remote
management service is mandatory in three classes and unnecessary on the field laptop. The print
spooler is only required at the branch endpoint. Network boot only on the collector machine. The
package manager hook only on the processing server.

These eight items produce 27 decisions, and a single corporate "removal list" cannot resolve them.
If the list removes all eight items, management access closes in three classes; if it removes none
of them, an unnecessary point stays open on the field laptop. The correct approach is to decide per
class, and that is exactly the maintenance cost: four lists, 84 decisions, and 8 boundary items that
have to be revisited with every role change.

## The Cost of Closing

The cost column shows 41 manual steps, and its distribution is also reversed: the two classes with
the most points closed pay the highest cost (15 and 15), the two classes with the least closed pay
the lowest (5 and 6). The type table shows a sharper ratio: logon scripts run 10 cost per 4 closed
points, service definitions run 6 cost per 12 closed points. Closing items users install themselves
costs roughly five times as much as closing service definitions — because the user installed that
item to get a job done, and once the item closes, the job falls to management.

The cost becomes concrete once items are read by name. The recovery boot entry is surplus in all
four classes, and removing it requires portable media for on-site repair — 8 manual steps across the
four classes. The user task folder and the startup folder are how users set up their own scheduling;
once closed, every request turns into a management task.

The package side is counted separately: 6 removal decisions and 14 manual steps. The diagnostic
toolkit is surplus in all four classes, but troubleshooting is done with it; removing it means a
reinstall on every incident. This package is not a foothold point — it does not run by itself — but
as long as it sits on disk it serves a point that could call it. The same item will be counted again
in the application allowlist lesson.

## Summary

- A foothold point is an item that requires nobody to do anything for it to run: a boot entry, a
  service definition, a scheduled task, a logon script, and a configuration hook. An installed
  package does not belong to this set and is counted separately.
- Of 101 installed-item decisions across four machine classes, 84 are foothold points; clearing
  unnecessary items closes 40 of them and leaves 44 open. The only thing that can be done for what
  stays open is to write it to a baseline — that is not a closure, it is making it visible.
- Service definitions are the largest piece of the remaining side: 19 of 31 points cannot be
  removed, because the items that make an endpoint manageable are also the ways to persist there.
- Of 19 points definable without administrator privilege, 17 close and 2 remain. Who can write a
  point carries more decision weight than how many there are.
- Eight items diverge by class and produce 27 decisions; a single corporate list resolves them
  either as unnecessary work in one class or as incomplete closure in another.
- The cost of closing is 41 manual steps and is highest in the classes cleaned the most; the cost
  per closed point in logon scripts is roughly five times that of service definitions.

## Next Step

The remaining 44 points cannot be closed, because the work requires them. But the components
carrying these points age where they stand: once a flaw is announced in the binary behind a service
definition, that point, because it cannot be removed, stays usable until a patch is applied. The
next lesson's measure is therefore the **window**: the days between announcement and application,
the number of points that stay open in that window, and the product of the two. An emergency patch
shortens the window, but how many test steps it skips and how many outages it produces are counted
separately. What a patch actually closes is also a separate question: a patch does not remove a
foothold point, it only closes one known path to reaching that point.
