build · Stage 2
脅威モデルを設計・検証・残余リスクへ接続する
資産、actor、trust boundary、data flowから外部・内部者脅威を導き、controlと検証を追跡可能にする。
到達目標
資産、ID・type・scopeを持つactor、trust boundary、参照整合したdata flow、攻撃経路を一つの脅威モデルとして追跡できる
- 資産、境界、data flow、外部・内部者脅威、型付けcontrol、verification、decisionとuncertaintyを持つ残余riskを結ぶ機械可読モデル
- secure by designとchecklist適合が同義ではない理由を説明する6分間の解説
各脅威を型付けしたprevent・detect・recover controlと、control単位の具体的なverificationへ対応付けられる
- 資産、境界、data flow、外部・内部者脅威、型付けcontrol、verification、decisionとuncertaintyを持つ残余riskを結ぶ機械可読モデル
- 脅威仮説ごとに予防・検知・回復と検証を選び、残余riskを受容または低減する判断記録
悪意、誤操作、資格情報悪用を区別し、過剰監視に依存しない内部者脅威対策を設計できる
- 脅威仮説ごとに予防・検知・回復と検証を選び、残余riskを受容または低減する判断記録
- 正規権限を持つ委託運用者の誤操作と資格情報悪用を含む内部者脅威へ移したモデル
能力の進行
recognize
資産、actor、entry point、trust boundary、data flow、threat、controlを区別できる
証拠: 資産、境界、data flow、外部・内部者脅威、型付けcontrol、verification、decisionとuncertaintyを持つ残余riskを結ぶ機械可読モデル
explain
secure by design、secure by default、defense in depth、least privilegeの責任境界を説明できる
証拠: secure by designとchecklist適合が同義ではない理由を説明する6分間の解説
apply
固定scenarioから外部・内部者の攻撃経路を列挙し、各脅威のprevent・detect・recover controlとverificationを追跡できる
証拠: 資産、境界、data flow、外部・内部者脅威、型付けcontrol、verification、decisionとuncertaintyを持つ残余riskを結ぶ機械可読モデル
diagnose
controlが未検証、迂回可能、または検知・回復不足である状態を反証できる
証拠: 脅威仮説ごとに予防・検知・回復と検証を選び、残余riskを受容または低減する判断記録
lead
開発、運用、人事、法務、privacy担当と残余riskのowner・期限を合意し、脅威モデルreviewを主導できる
証拠: 正規権限を持つ委託運用者の誤操作と資格情報悪用を含む内部者脅威へ移したモデル
なぜ重要か
secure by designは、出荷前に脆弱性名を列挙する作業ではない。顧客が守りたい資産と現実のdata flowを起点に、設計decision、default、実装control、検証、回復へ責任を接続する継続的なengineeringである。
内部者脅威も「悪意ある従業員」に限定できない。正規権限の誤操作、委託先credentialの盗用、過剰権限、手順の曖昧さが同じ資産へ到達する。人の意図を推測するより、行為と境界を制約し、privacyを守りながら検証可能な証拠を残す。
メンタルモデル
assetから逆向きに考える。誰がどのentry pointから、どのtrust boundaryとdata flowを通ってassetへ作用できるかを記述し、その経路を短くする、権限を狭める、検知する、回復するcontrolを配置する。
| 脅威経路 | 予防 | 検知・回復 | privacy境界 |
|---|---|---|---|
| 外部credential悪用 | 短命token・rate limit | 異常検知・即時revoke | 必要最小限の認証event |
| 委託者の誤bulk export | purpose別権限・二者承認 | 件数上限・取消可能なjob | 内容でなく操作metadataを監査 |
| 管理credentialの盗用 | workload identity・step-up | 署名audit・credential rotation | 個人追跡はincident目的へ限定 |
注記
図を読む際の補足情報です。
- この注記は旧図の読み順を保持する補助です。
- asset: customer data、deployment credential、audit logの価値と安全性目標を定義する。
- actorとboundary: external customer、operations contractor、platform adminをID・type・scopeを持つentityとして定義し、flowの起点と越境点へ参照させる。
- threat: 意図ではなく、credential再利用、誤ったbulk export、audit停止など観測可能な行為で表す。
- control: prevent、detect、recoverを型として区別し、各threatへ三種類すべてを重ねる。
- verification: 具体的なtest ID、control ID、resultをthreatへ接続する。
- residual risk: threat ID、decision、uncertainty、owner、期限を付け、未解決を隠さない。
各 threat をどの control、verification、残余 risk の判断まで追跡できるか。
- threat trace
- 資産から残余riskの意思決定までをIDで追跡する安全設計モデル。
- asset
- customer data、deployment credential、audit logの価値と安全性目標を定義する。
- component
- threat trace
- actorとboundary
- external customer、operations contractor、platform adminをID・type・scopeを持つentityとして定義し、flowの起点と越境点へ参照させる。
- component
- threat trace
- threat
- 意図ではなく、credential再利用、誤ったbulk export、audit停止など観測可能な行為で表す。
- component
- threat trace
- control
- prevent、detect、recoverを型として区別し、各threatへ三種類すべてを重ねる。
- component
- threat trace
- verification
- 具体的なtest ID、control ID、resultをthreatへ接続する。
- component
- threat trace
- residual risk
- threat ID、decision、uncertainty、owner、期限を付け、未解決を隠さない。
- component
- threat trace
- asset → actorとboundary: 価値ある対象と越境点を対応付ける
- actorとboundary → threat: actorの行為として脅威を具体化する
- threat → control: 予防・検知・回復を重ねる
- control → verification: test結果で有効性を確かめる
- verification → residual risk: 未解決riskの判断根拠にする
assetと境界から観測可能なthreatをたどり、予防・検知・回復、test結果、残余riskの所有者まで説明できる。
動く例で考える
固定された運用portalモデルのtraceabilityを検証する
- 前提
- 三資産、三actor、internet・operations zone・production境界を持つ。実system、credential、利用者data、攻撃payloadは使わない。
- 入力
- 外部credential悪用、委託者の誤bulk export、委託credential悪用の3脅威をharmlessなIDと関係だけで表す。
- 操作
- 各脅威からactor、asset、flow、prevent・detect・recover control、verification、残余riskへIDを接続し、actorとflow起点の整合、一意ID、canonical ISO date、空集合、欠落参照、failed result、owner空欄、control型欠落を検査する。
- 観測
- 外部・内部者の両actor typeが存在し、全flowのfromが既知actor、toが既知zoneを参照する。全脅威に三種類のcontrolと各controlのpassed verificationがあり、残余riskに一意ID、threat ID、decision、uncertainty、owner、canonical review dateがある。13種の壊れたmodelはすべてrejectされる。
- 結論
- modelは安全の証明ではなく、現在の仮説と検証範囲を可視化する。変更、incident、test失敗ごとに再評価する。
次のPython 3.13 harnessは固定JSON相当の関係だけを検査し、攻撃codeを実行せず、networkや秘密情報へアクセスしない。
python3.13 - <<'PY'
import copy
from datetime import date
import json
HARNESS = "threat_model_lab_v1"
REQUIRED_CONTROL_TYPES = {"prevent", "detect", "recover"}
ALLOWED_RISK_DECISIONS = {"accept", "avoid", "mitigate", "transfer"}
assets = [
{"id": "customer-data", "goal": "confidentiality-and-integrity"},
{"id": "deployment-credential", "goal": "confidentiality-and-limited-use"},
{"id": "audit-log", "goal": "integrity-and-availability"},
]
actors = [
{
"id": "external-customer",
"type": "external",
"scope": ["self-service-login"],
},
{
"id": "operations-contractor",
"type": "insider",
"scope": ["approved-export", "approved-deployment-window"],
},
{
"id": "platform-admin",
"type": "insider",
"scope": ["platform-administration"],
},
]
trust_boundaries = [
{"id": "internet-edge", "from": "internet", "to": "operations-zone"},
{"id": "production-edge", "from": "operations-zone", "to": "production"},
]
cross_boundary_flows = [
{
"id": "flow-login",
"from": "external-customer",
"to": "operations-zone",
"crosses": ["internet-edge"],
},
{
"id": "flow-export",
"from": "operations-contractor",
"to": "production",
"crosses": ["production-edge"],
},
{
"id": "flow-deploy",
"from": "operations-contractor",
"to": "production",
"crosses": ["production-edge"],
},
]
threats = [
{
"id": "T-EXT-CREDENTIAL",
"actor_id": "external-customer",
"actor_type": "external",
"asset_id": "customer-data",
"flow_id": "flow-login",
"scenario": "reused credential reaches customer records",
},
{
"id": "T-IN-BULK-ERROR",
"actor_id": "operations-contractor",
"actor_type": "insider",
"asset_id": "customer-data",
"flow_id": "flow-export",
"scenario": "authorized operator selects an excessive export scope",
},
{
"id": "T-IN-CREDENTIAL",
"actor_id": "operations-contractor",
"actor_type": "insider",
"asset_id": "deployment-credential",
"flow_id": "flow-deploy",
"scenario": "contractor credential is used outside an approved window",
},
]
controls = [
{
"id": "short-lived-token",
"type": "prevent",
"purpose": "limit credential reuse window",
},
{
"id": "auth-anomaly-alert",
"type": "detect",
"purpose": "detect unusual session and tenant access",
},
{
"id": "session-revoke",
"type": "recover",
"purpose": "revoke affected sessions without waiting for expiry",
},
{
"id": "purpose-scope",
"type": "prevent",
"purpose": "bound export records to an approved purpose",
},
{
"id": "export-volume-alert",
"type": "detect",
"purpose": "detect export volume outside the approved envelope",
},
{
"id": "cancelable-job",
"type": "recover",
"purpose": "stop and remove an erroneous pending export",
},
{
"id": "approved-window",
"type": "prevent",
"purpose": "reject deployment credentials outside an approved window",
},
{
"id": "signed-audit",
"type": "detect",
"purpose": "detect unapproved credential use with tamper evidence",
},
{
"id": "credential-rotation",
"type": "recover",
"purpose": "replace exposed deployment credentials",
},
]
control_links = [
{
"threat_id": "T-EXT-CREDENTIAL",
"control_ids": [
"short-lived-token",
"auth-anomaly-alert",
"session-revoke",
],
},
{
"threat_id": "T-IN-BULK-ERROR",
"control_ids": [
"purpose-scope",
"export-volume-alert",
"cancelable-job",
],
},
{
"threat_id": "T-IN-CREDENTIAL",
"control_ids": [
"approved-window",
"signed-audit",
"credential-rotation",
],
},
]
verification_links = [
{
"threat_id": "T-EXT-CREDENTIAL",
"control_id": "short-lived-token",
"verification_id": "V-AUTH-PREVENT-01",
"result": "passed",
},
{
"threat_id": "T-EXT-CREDENTIAL",
"control_id": "auth-anomaly-alert",
"verification_id": "V-AUTH-DETECT-02",
"result": "passed",
},
{
"threat_id": "T-EXT-CREDENTIAL",
"control_id": "session-revoke",
"verification_id": "V-AUTH-RECOVER-03",
"result": "passed",
},
{
"threat_id": "T-IN-BULK-ERROR",
"control_id": "purpose-scope",
"verification_id": "V-EXPORT-PREVENT-01",
"result": "passed",
},
{
"threat_id": "T-IN-BULK-ERROR",
"control_id": "export-volume-alert",
"verification_id": "V-EXPORT-DETECT-02",
"result": "passed",
},
{
"threat_id": "T-IN-BULK-ERROR",
"control_id": "cancelable-job",
"verification_id": "V-EXPORT-RECOVER-03",
"result": "passed",
},
{
"threat_id": "T-IN-CREDENTIAL",
"control_id": "approved-window",
"verification_id": "V-DEPLOY-PREVENT-01",
"result": "passed",
},
{
"threat_id": "T-IN-CREDENTIAL",
"control_id": "signed-audit",
"verification_id": "V-DEPLOY-DETECT-02",
"result": "passed",
},
{
"threat_id": "T-IN-CREDENTIAL",
"control_id": "credential-rotation",
"verification_id": "V-DEPLOY-RECOVER-03",
"result": "passed",
},
]
residual_risks = [
{
"id": "R-SESSION-THEFT",
"threat_id": "T-EXT-CREDENTIAL",
"decision": "mitigate",
"uncertainty": "device-bound sessions are not available on one client",
"owner": "security-product-owner",
"review_date": "2026-10-30",
},
{
"id": "R-APPROVER-COLLUSION",
"threat_id": "T-IN-BULK-ERROR",
"decision": "accept",
"uncertainty": "two approvers can still coordinate an excessive export",
"owner": "operations-director",
"review_date": "2026-09-30",
},
{
"id": "R-AUDIT-DELAY",
"threat_id": "T-IN-CREDENTIAL",
"decision": "mitigate",
"uncertainty": "alert delivery may be delayed during a regional outage",
"owner": "platform-security-lead",
"review_date": "2026-09-15",
},
]
def validate_model(model):
errors = set()
required_collections = (
"assets",
"actors",
"trust_boundaries",
"cross_boundary_flows",
"threats",
"controls",
"control_links",
"verification_links",
"residual_risks",
)
for collection in required_collections:
if not model.get(collection):
errors.add(f"empty:{collection}")
def indexed(items, label):
result = {}
for item in items:
item_id = item.get("id")
if not item_id:
errors.add(f"missing-id:{label}")
elif item_id in result:
errors.add(f"duplicate-id:{label}:{item_id}")
else:
result[item_id] = item
return result
def require_unique_field(items, field, label):
seen = set()
for item in items:
value = str(item.get(field, "")).strip()
if not value:
errors.add(f"missing-id:{label}")
elif value in seen:
errors.add(f"duplicate-id:{label}:{value}")
else:
seen.add(value)
asset_by_id = indexed(model.get("assets", []), "asset")
actor_by_id = indexed(model.get("actors", []), "actor")
boundary_by_id = indexed(
model.get("trust_boundaries", []),
"boundary",
)
flow_by_id = indexed(
model.get("cross_boundary_flows", []),
"flow",
)
threat_by_id = indexed(model.get("threats", []), "threat")
control_by_id = indexed(model.get("controls", []), "control")
require_unique_field(
model.get("verification_links", []),
"verification_id",
"verification",
)
require_unique_field(
model.get("residual_risks", []),
"id",
"residual-risk",
)
for actor_id, actor in actor_by_id.items():
if actor.get("type") not in {"external", "insider"}:
errors.add(f"invalid-actor-type:{actor_id}")
if not actor.get("scope"):
errors.add(f"missing-actor-scope:{actor_id}")
for control_id, control in control_by_id.items():
control_type = control.get("type")
if control_type not in REQUIRED_CONTROL_TYPES:
errors.add(f"invalid-control-type:{control_id}")
known_zones = {
zone
for boundary in boundary_by_id.values()
for zone in (boundary.get("from"), boundary.get("to"))
if zone
}
for flow_id, flow in flow_by_id.items():
if flow.get("from") not in actor_by_id:
errors.add(f"unknown-flow-actor:{flow_id}")
if flow.get("to") not in known_zones:
errors.add(f"unknown-flow-zone:{flow_id}")
crossed = flow.get("crosses", [])
if not crossed:
errors.add(f"missing-boundary-crossing:{flow_id}")
for boundary_id in crossed:
if boundary_id not in boundary_by_id:
errors.add(f"unknown-boundary:{flow_id}:{boundary_id}")
for threat_id, threat in threat_by_id.items():
if threat.get("asset_id") not in asset_by_id:
errors.add(f"unknown-asset:{threat_id}")
if threat.get("flow_id") not in flow_by_id:
errors.add(f"unknown-flow:{threat_id}")
actor_id = threat.get("actor_id")
actor = actor_by_id.get(actor_id)
if actor is None:
errors.add(f"unknown-threat-actor:{threat_id}:{actor_id}")
elif actor.get("type") != threat.get("actor_type"):
errors.add(f"actor-type-mismatch:{threat_id}")
flow = flow_by_id.get(threat.get("flow_id"))
if flow is not None and flow.get("from") != actor_id:
errors.add(f"actor-flow-mismatch:{threat_id}")
if threat.get("actor_type") not in {"external", "insider"}:
errors.add(f"invalid-actor-type:{threat_id}")
linked_controls = {threat_id: set() for threat_id in threat_by_id}
for link in model.get("control_links", []):
threat_id = link.get("threat_id")
if threat_id not in threat_by_id:
errors.add(f"unknown-control-threat:{threat_id}")
continue
for control_id in link.get("control_ids", []):
if control_id not in control_by_id:
errors.add(f"unknown-control:{threat_id}:{control_id}")
else:
linked_controls[threat_id].add(control_id)
for threat_id, control_ids in linked_controls.items():
linked_types = {
control_by_id[control_id].get("type")
for control_id in control_ids
}
if linked_types != REQUIRED_CONTROL_TYPES:
errors.add(f"incomplete-control-types:{threat_id}")
verified_controls = {threat_id: set() for threat_id in threat_by_id}
for verification in model.get("verification_links", []):
threat_id = verification.get("threat_id")
control_id = verification.get("control_id")
if threat_id not in threat_by_id:
errors.add(f"unknown-verification-threat:{threat_id}")
continue
if control_id not in control_by_id:
errors.add(f"unknown-verification-control:{control_id}")
continue
if control_id not in linked_controls[threat_id]:
errors.add(f"unlinked-verification:{threat_id}:{control_id}")
if verification.get("result") != "passed":
errors.add(
f"verification-not-passed:"
f"{verification.get('verification_id', 'unknown')}"
)
else:
verified_controls[threat_id].add(control_id)
for threat_id, control_ids in linked_controls.items():
if verified_controls[threat_id] != control_ids:
errors.add(f"unverified-controls:{threat_id}")
risks_by_threat = {threat_id: 0 for threat_id in threat_by_id}
for risk in model.get("residual_risks", []):
threat_id = risk.get("threat_id")
if threat_id not in threat_by_id:
errors.add(f"unknown-risk-threat:{threat_id}")
continue
risks_by_threat[threat_id] += 1
if not str(risk.get("owner", "")).strip():
errors.add(f"blank-risk-owner:{risk.get('id', 'unknown')}")
if not risk.get("review_date"):
errors.add(f"missing-risk-review:{risk.get('id', 'unknown')}")
else:
review_date = risk.get("review_date")
try:
parsed_review_date = date.fromisoformat(review_date)
except (TypeError, ValueError):
errors.add(f"invalid-risk-review:{risk.get('id', 'unknown')}")
else:
if parsed_review_date.isoformat() != review_date:
errors.add(
f"noncanonical-risk-review:"
f"{risk.get('id', 'unknown')}"
)
if risk.get("decision") not in ALLOWED_RISK_DECISIONS:
errors.add(f"invalid-risk-decision:{risk.get('id', 'unknown')}")
if not str(risk.get("uncertainty", "")).strip():
errors.add(
f"missing-risk-uncertainty:{risk.get('id', 'unknown')}"
)
for threat_id, count in risks_by_threat.items():
if count == 0:
errors.add(f"missing-residual-risk:{threat_id}")
# Sorting makes validation evidence deterministic across Python runs.
return sorted(errors)
model = {
"assets": assets,
"actors": actors,
"trust_boundaries": trust_boundaries,
"cross_boundary_flows": cross_boundary_flows,
"threats": threats,
"controls": controls,
"control_links": control_links,
"verification_links": verification_links,
"residual_risks": residual_risks,
}
validation_errors = validate_model(model)
assert validation_errors == [], validation_errors
# Every mutation is harmless relationship data. Running it demonstrates that
# the validator rejects false-green traceability without executing an attack.
mutation_models = {}
mutation_models["empty_controls"] = copy.deepcopy(model)
mutation_models["empty_controls"]["controls"] = []
mutation_models["unknown_reference"] = copy.deepcopy(model)
mutation_models["unknown_reference"]["threats"][0][
"asset_id"
] = "unknown-asset"
mutation_models["failed_verification"] = copy.deepcopy(model)
mutation_models["failed_verification"]["verification_links"][0][
"result"
] = "failed"
mutation_models["blank_owner"] = copy.deepcopy(model)
mutation_models["blank_owner"]["residual_risks"][0]["owner"] = " "
mutation_models["missing_control_type"] = copy.deepcopy(model)
del mutation_models["missing_control_type"]["controls"][0]["type"]
mutation_models["empty_actors"] = copy.deepcopy(model)
mutation_models["empty_actors"]["actors"] = []
mutation_models["unknown_actor"] = copy.deepcopy(model)
mutation_models["unknown_actor"]["threats"][0]["actor_id"] = "unknown-actor"
mutation_models["actor_flow_mismatch"] = copy.deepcopy(model)
mutation_models["actor_flow_mismatch"]["cross_boundary_flows"][0][
"from"
] = "platform-admin"
mutation_models["blank_verification_id"] = copy.deepcopy(model)
mutation_models["blank_verification_id"]["verification_links"][0][
"verification_id"
] = " "
mutation_models["duplicate_verification_id"] = copy.deepcopy(model)
mutation_models["duplicate_verification_id"]["verification_links"][1][
"verification_id"
] = mutation_models["duplicate_verification_id"]["verification_links"][0][
"verification_id"
]
mutation_models["blank_risk_id"] = copy.deepcopy(model)
mutation_models["blank_risk_id"]["residual_risks"][0]["id"] = ""
mutation_models["duplicate_risk_id"] = copy.deepcopy(model)
mutation_models["duplicate_risk_id"]["residual_risks"][1][
"id"
] = mutation_models["duplicate_risk_id"]["residual_risks"][0]["id"]
mutation_models["invalid_review_date"] = copy.deepcopy(model)
mutation_models["invalid_review_date"]["residual_risks"][0][
"review_date"
] = "2026-02-30"
negative_mutations = {}
for name, mutated_model in mutation_models.items():
mutation_errors = validate_model(mutated_model)
negative_mutations[name] = {
"rejected": bool(mutation_errors),
"errors": mutation_errors,
}
assert all(
mutation["rejected"] and mutation["errors"]
for mutation in negative_mutations.values()
)
report = {
"harness": HARNESS,
"fixture": "operations-portal-v1",
**model,
"attack_paths": [
{
"threat_id": threat["id"],
"actor_id": threat["actor_id"],
"flow_id": threat["flow_id"],
"asset_id": threat["asset_id"],
}
for threat in threats
],
"model_validation": {
"valid": not validation_errors,
"errors": validation_errors,
"actor_count": len(actors),
"threat_count": len(threats),
"control_count": len(controls),
"verification_count": len(verification_links),
"residual_risk_count": len(residual_risks),
},
"negative_mutations": negative_mutations,
"attack_code_executed": False,
"external_network_used": False,
}
print(json.dumps(report, ensure_ascii=False, sort_keys=True))
PY
トレードオフと失敗モード
- 誤診: SSDF、ASVS、threat checklistを満たせば安全が保証される。 反証: frameworkは活動とcontrolの網羅性を助けるが、対象model、実装、verification、将来変更の正しさを自動では保証しない。
- 誤診: 内部者脅威は悪意ある従業員だけなので、個人監視を増やせば解決する。 反証: 誤操作、盗用credential、曖昧な手順も同じ経路を作る。権限・承認・recoveryを改善し、privacyに比例した監査を選ぶ。
- 予防偏重: 完全防止だけを目指すと、侵害を前提にした検知、revoke、復旧演習が抜ける。
- 責任転嫁: 安全機能を有料optionや利用者設定へ寄せると、安全なdefaultと顧客成果が失われる。
- model陳腐化: 新しいdata flow、権限、supplierをmodelへ戻さなければ、過去のgreenは現在を表さない。
知識チェック
- assetを先に定義せず脅威名から始めると、優先順位に何が欠けるか。
- 正規権限の誤操作とcredential盗用に共通して効き、個人の悪意を仮定しないcontrolを二つ示せ。
- controlが実装済みでもverification linkを要求する理由は何か。
- 予防controlを迂回された後の検知と回復を一つずつ挙げよ。
- 残余riskのownerとreview dateが空なら、出荷判断はなぜ再現不能になるか。
出典と次の学習
NIST SSDFはsecure development実践を、CISAのSecure by Design guidanceは顧客のsecurity outcomeと安全なdefaultへの製造者責任を整理する。OWASP Threat Modeling Cheat SheetとASVS 5.0.0はmodel作成とverification要求を結ぶ。CISA Insider Threat Mitigation Guideは内部者を悪意だけへ限定しない多面的な低減を扱う。
次はdataとscaleのtrackで、保存する資産、transaction境界、分散failureをmodelへ追加する。設計・権限・data flowが変わるたびに脅威modelとverificationを同じ変更で更新する。
実践ラボ
運用portalの外部・内部者脅威を安全に追跡する
提出成果物: 資産、境界、攻撃経路、検証を結ぶ脅威モデル
- customer data、deployment credential、audit logを資産として、外部利用者、委託運用者、管理者をactorとして列挙する
- internet、operations zone、productionのtrust boundaryを越えるdata flowを固定fixtureへ記録し、fromをactor ID、toを既知zoneへ参照させる
- 外部actorと内部者について、対象資産とflowを結ぶharmlessな攻撃経路仮説を作る
- 各脅威へ型付けしたprevent・detect・recover controlを割り当て、controlごとのverification IDと結果を対応付ける
- verificationと残余riskのIDを一意にし、残余riskへthreat ID、decision、uncertainty、owner、canonical ISO review dateを付け、actor・ID・dateを含む13 mutationがrejectされることを確認する
説明して理解を確かめる
6分で、frameworkやchecklist適合が安全を保証しない理由、内部者が悪意ある従業員だけではない理由、least privilegeとprivacyを両立する方法を説明する。
アセスメント
問い: 正規権限を持つ委託運用者のcredentialが盗用された。通常操作との違いをどのcontrolと証拠で検知するか。
期待する証拠: 短命credential、step-up認証、操作範囲、承認分離、改ざん耐性audit、異常検知、revoke演習の対応
問い: ASVS項目を満たしたため安全だという主張を、どのように反証可能へ変えるか。
期待する証拠: 対象assetとthreat、実装control、具体的test、結果、残余risk、再review条件へのtraceability
別問題へ転用する
正規権限を持つ委託運用者の誤操作・資格情報悪用を含む内部者脅威へモデルを移す
復習スケジュール
- 1日後
checklist適合を安全保証へ読み替えられない理由を一つの反例で示す
- 7日後
内部者脅威で悪意を仮定せずに設計できるcontrolを三つ挙げる
- 30日後
一つの残余riskをowner、review date、verificationへ接続する
- 90日後
checklist適合を安全保証へ読み替えられない理由を一つの反例で示す
評価ルーブリック
| 観点 | 未達 | 発展途上 | 熟達 | 卓越 |
|---|---|---|---|---|
| technical-correctness | 脆弱性名だけを列挙し、資産、境界、flowと結ばない | 脅威とcontrolはあるが、内部者またはverificationが抜ける | 外部・内部者脅威を資産、flow、control、verification、残余riskへ追跡する | 予防・検知・回復、credential lifecycle、supply chain、privacy境界まで統合する |
| judgment | すべてのriskを監視強化または利用者設定へ転嫁する | controlを追加するが、利用者負担、privacy、運用失敗を比較しない | 影響、可能性、control費用、privacy、回復性から優先順位を選ぶ | 安全なdefaultと顧客成果を製品責任として扱い、残余riskを経営判断へ上げる |
| evidence | checklistのchecked状態だけを示し、実行結果がない | test結果はあるが、どのthreatとassetを検証したか分からない | threat ID、control ID、verification ID、result、owner、review dateを結ぶ | 設計変更、incident、test失敗を脅威モデル更新へ自動的に戻す |
| communication | 内部者を一括して疑い、過剰な個人監視を結論にする | 技術用語はあるが、責任・privacy・残余riskが不明である | actorの意図を断定せず、行為、資産、境界、control、ownerを明示する | 開発・運用・法務・privacy担当が同じmodelで優先順位と期限を合意できる |
出典
以下の外部資料は利用者が選択したときだけ開きます。
- Secure Software Development Framework (SSDF) Version 1.1: Recommendations for Mitigating the Risk of Software Vulnerabilities (standard)
- Shifting the Balance of Cybersecurity Risk: Principles and Approaches for Secure by Design Software (primary)
- Threat Modeling Cheat Sheet (primary)
- OWASP Application Security Verification Standard 5.0.0 (standard)
- Insider Threat Mitigation Guide (primary)