# Threat hunting, detection and lessons learned

Move from IOC lists to testable hypotheses. Validate rules against positive and benign negative examples, and turn closure into durable improvements.

Original AI-assisted SecNet educational content. Fictional local examples; not a standards provider publication, endorsement or compliance evidence. Page exercises do not create account progress.

Sources checked: 2026-10-21

## IOC and behavioral hypotheses

IP/domain/hash lists alone are not durable detection strategy. Record source/date, context, expiry and ownership. Translate behavior into a hypothesis and required data sources. ATT&CK techniques provide analytic vocabulary, not automatic attribution or coverage percentages. Harbor addresses are fictional and must not become real blocklists. Include legitimate maintenance behavior in hypothesis testing.

## Positive and negative controls

Returning rows does not establish correct alerts. Design known supporting records and benign negative controls, fixing fields, time window and sensor scope. Retest mappings and timestamp semantics when moving rules to another environment. This desk flags same-user sign-ins within ten minutes after a click; it is not a threat detector or production SIEM query.

## Ownership, cost and review

Assess volume, investigation time, false positives and missed-detection risk together. Missing data is not absence of attacks. Assign a rule owner, escalation path and review date. Connect detection improvements to training, access controls and process changes. At closure, record failed assumptions, added regression tests and residual-risk ownership.

## Compare Harbor-17 with maintenance

Harbor-17 has a same-user click/sign-in correlation two minutes apart. The maintenance fixture has no click, so no such hypothesis is generated. This proves neither benign maintenance nor malicious Harbor-17, only the narrow rule’s behavior. Test other users, prior sign-ins and events outside ten minutes to expose boundaries. Closure records should carry those limits, data gaps and the next review.

## Working sequence

1. Write a sourced, time-bounded hypothesis.
2. Verify required fields and coverage.
3. Test positive and benign negative controls.
4. Assign alert ownership and escalation.
5. Close learning through regression and risk handover.

## 1. It is on the list

IOC presence is not current context or attribution. Require source date, behavioral mapping, context and expiry ownership before broadening decisions.

This code is a local policy model; it does not replace target/cluster testing.

### Weak implementation
```js
s=>s.iocPresent
```

### Corrected implementation
```js
s=>s.iocPresent&&s.sourceDated&&s.contextReviewed&&s.behaviorMapped&&s.expiryOwned
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "supported-case",
      "input": {
        "iocPresent": true,
        "sourceDated": true,
        "contextReviewed": true,
        "behaviorMapped": true,
        "expiryOwned": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "undated-list",
      "input": {
        "iocPresent": true,
        "sourceDated": false,
        "contextReviewed": false,
        "behaviorMapped": false,
        "expiryOwned": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "no-expiry-owner",
      "input": {
        "iocPresent": true,
        "sourceDated": true,
        "contextReviewed": true,
        "behaviorMapped": true,
        "expiryOwned": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "supported-case",
      "input": {
        "iocPresent": true,
        "sourceDated": true,
        "contextReviewed": true,
        "behaviorMapped": true,
        "expiryOwned": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "undated-list",
      "input": {
        "iocPresent": true,
        "sourceDated": false,
        "contextReviewed": false,
        "behaviorMapped": false,
        "expiryOwned": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "no-expiry-owner",
      "input": {
        "iocPresent": true,
        "sourceDated": true,
        "contextReviewed": true,
        "behaviorMapped": true,
        "expiryOwned": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## 2. The query returns rows

Row count does not prove rule quality. Validate positive and benign negative examples alongside coverage and ownership.

This code is a local policy model; it does not replace target/cluster testing.

### Weak implementation
```js
s=>s.queryReturnsRows
```

### Corrected implementation
```js
s=>s.queryReturnsRows&&s.positiveFixture&&s.negativeFixture&&s.coverageKnown&&s.ownerAssigned
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "supported-case",
      "input": {
        "queryReturnsRows": true,
        "positiveFixture": true,
        "negativeFixture": true,
        "coverageKnown": true,
        "ownerAssigned": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "rows-only",
      "input": {
        "queryReturnsRows": true,
        "positiveFixture": false,
        "negativeFixture": false,
        "coverageKnown": false,
        "ownerAssigned": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "no-benign-control",
      "input": {
        "queryReturnsRows": true,
        "positiveFixture": true,
        "negativeFixture": false,
        "coverageKnown": true,
        "ownerAssigned": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "supported-case",
      "input": {
        "queryReturnsRows": true,
        "positiveFixture": true,
        "negativeFixture": true,
        "coverageKnown": true,
        "ownerAssigned": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "rows-only",
      "input": {
        "queryReturnsRows": true,
        "positiveFixture": false,
        "negativeFixture": false,
        "coverageKnown": false,
        "ownerAssigned": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "no-benign-control",
      "input": {
        "queryReturnsRows": true,
        "positiveFixture": true,
        "negativeFixture": false,
        "coverageKnown": true,
        "ownerAssigned": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## 3. Ticket closed

Ticket status is not a learning loop. Regression, residual-risk ownership, handover and scheduled review are needed to support durable improvement.

This code is a local policy model; it does not replace target/cluster testing.

### Weak implementation
```js
s=>s.ticketClosed
```

### Corrected implementation
```js
s=>s.ticketClosed&&s.residualRiskOwned&&s.regressionAdded&&s.handoverComplete&&s.reviewScheduled
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "supported-case",
      "input": {
        "ticketClosed": true,
        "residualRiskOwned": true,
        "regressionAdded": true,
        "handoverComplete": true,
        "reviewScheduled": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "closure-only",
      "input": {
        "ticketClosed": true,
        "residualRiskOwned": false,
        "regressionAdded": false,
        "handoverComplete": false,
        "reviewScheduled": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "no-retest",
      "input": {
        "ticketClosed": true,
        "residualRiskOwned": true,
        "regressionAdded": false,
        "handoverComplete": true,
        "reviewScheduled": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "supported-case",
      "input": {
        "ticketClosed": true,
        "residualRiskOwned": true,
        "regressionAdded": true,
        "handoverComplete": true,
        "reviewScheduled": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "closure-only",
      "input": {
        "ticketClosed": true,
        "residualRiskOwned": false,
        "regressionAdded": false,
        "handoverComplete": false,
        "reviewScheduled": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "no-retest",
      "input": {
        "ticketClosed": true,
        "residualRiskOwned": true,
        "regressionAdded": false,
        "handoverComplete": true,
        "reviewScheduled": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## Exit evidence

- Hypothesis/field/window/coverage test matrix
- Positive/negative outcomes and learning record

## References

- [NIST SP 800-61 Rev.3 · Final April 2025](https://csrc.nist.gov/pubs/sp/800/61/r3/final)
- [MITRE ATT&CK · Phishing T1566](https://attack.mitre.org/techniques/T1566/)
