@chrono-meta/fh-gate 2.5.1 โ 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/fh_4axis_gate.md +26 -3
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +24 -3
- package/CATALOG.md +41 -0
- package/CLAUDE.md +77 -15
- package/README.ja.md +36 -11
- package/README.ko.md +36 -11
- package/README.md +116 -18
- package/README.zh.md +32 -11
- package/docs/USER_GUIDE.md +118 -0
- package/docs/platform_sustainability.md +174 -0
- package/knowledge/shared/GLOSSARY.md +26 -1
- package/knowledge/shared/harness-core/fh_detail_protocols.md +44 -2
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +19 -5
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +129 -2
- package/knowledge/shared/harness-core/multi_model_sidecar_strategy.md +32 -0
- package/knowledge/shared/harness-core/ship_readiness_gate.md +77 -8
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +110 -0
- package/knowledge/shared/rules/knowledge_layer_seam.md +1 -1
- package/package.json +17 -3
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +136 -0
- package/plugins/fh-meta/agents/persona-innovator.md +170 -0
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +44 -0
- package/plugins/fh-meta/skills/fh/SKILL.md +31 -0
- package/plugins/fh-meta/skills/goal-quench/SKILL_detail.md +1 -1
- package/plugins/fh-meta/skills/harness-doctor/SKILL.md +7 -1
- package/plugins/fh-meta/skills/harvest-loop/SKILL.md +32 -0
- package/plugins/fh-meta/skills/public-surface-audit/SKILL_detail.md +16 -1
- package/plugins/fh-meta/skills/steel-quench/SKILL.md +97 -0
- package/scripts/adapters/fixtures/mate_agent_boundary_known_negative.md +31 -0
- package/scripts/adapters/fixtures/mate_agent_boundary_known_positive.md +56 -0
- package/scripts/adapters/peer_resolve.sh +58 -6
- package/scripts/cluster_capability_scan.sh +60 -18
- package/scripts/digest_landing_check.sh +181 -10
- package/scripts/fh_hub_identity.sh +83 -0
- package/scripts/fh_session_load.sh +53 -5
- package/scripts/fh_track_resolve.sh +114 -0
- package/scripts/field_canon_preload.sh +50 -5
- package/scripts/package_coverage_check.sh +17 -0
- package/scripts/prior_art_prompt.sh +168 -0
- package/scripts/psa_scan_lib.sh +201 -15
- package/scripts/residency_admission_check.sh +204 -0
- package/scripts/selfcheck.sh +117 -1
- package/scripts/test_adapter_lanes.sh +67 -2
- package/scripts/test_heavy_classifier_lanes.sh +144 -0
- package/scripts/test_marker_defense_lanes.sh +152 -0
- package/scripts/test_marker_soul_check_lanes.sh +211 -0
- package/scripts/test_prior_art_prompt_lanes.sh +128 -0
- package/scripts/test_psa_singlefile_lanes.sh +351 -1
- package/scripts/test_residency_admission_lanes.sh +60 -0
- package/scripts/test_track_resolve_lanes.sh +158 -0
- package/templates/.git-hooks/pre-commit +400 -4
- package/templates/.git-hooks/pre-push +17 -2
- package/templates/settings.PriorArt.snippet.json +15 -0
- package/scripts/test_satellite_publish_gate_lanes.sh +0 -339
|
@@ -39,6 +39,14 @@ cd "$REPO_ROOT" || exit 1
|
|
|
39
39
|
# scripts/sync_to_be_lanes.sh โ forward-path lane suite for sync-to-be.sh, itself
|
|
40
40
|
# ACCEPTED_ABSENT above; added 2026-08-14, pmh-dev#69.
|
|
41
41
|
ACCEPTED_ABSENT=(
|
|
42
|
+
# ๐ฅ ๋๊ฐ๋ ์ง์ ์์ ๋ฆฐํธ์ ๊ทธ ๋ ์ธ โ **์ผ๋ถ๋ฌ ์ถํํ์ง ์๋๋ค.**
|
|
43
|
+
# ์ด ๊ฐ๋๋ ์ด์์ ๋ด๋ถ ํจํด์ธต(`.claude/rules/.public-surface-patterns`, gitignored)์ด ์์ผ๋ฉด
|
|
44
|
+
# fail-closed ๋ก ๋ง๋๋ค. ๊ทธ ์ธต์ ์๋น์์๊ฒ ์ ๋๊ฐ๋ฏ๋ก, ์ถํํ๋ฉด **์ ์ ํ ์ค์น๋ 100% ์ฐจ๋จ**๋๋ค
|
|
45
|
+
# โ CLAUDE.md ๊ฐ ยซ๋ชจ๋ ์ install ์ ๋ง๋ ๊ฒ์ดํธ๋ ์๊ฒฉํ ๊ฒ์ดํธ๊ฐ ์๋๋ผ ์ฐํ ํ๋ จ๊ธฐยป ๋ผ ๋ชป๋ฐ์
|
|
46
|
+
# ๊ทธ ํํ๋ค. ์๋น์์ฉ์ผ๋ก ์ด๋ ค๋ฉด override ๋ถ์ฌ ์์ degrade ๋ฅผ ๋ฐ๋ก ์ค๊ณํด์ผ ํ๊ณ , ์ ํ๋ค.
|
|
47
|
+
# `selfcheck.sh` ๋ ๋์ ๋ถ์ฌ ์ `_absent_subject_verdict` ๋ก ๋น ์ง๋ฏ๋ก ํจํค์ง ๋ชจ๋์์ ์ณ๊ฒ degrade ํ๋ค.
|
|
48
|
+
"scripts/outbound_query_guard.sh"
|
|
49
|
+
"scripts/test_outbound_query_lanes.sh"
|
|
42
50
|
".claude/registry/LOCAL_SKILL_REGISTRY.md"
|
|
43
51
|
# An INSTALL DESTINATION the user creates (`cp templates/local_fh_context.md
|
|
44
52
|
# .claude/rules/local_fh_context.md`), not a file FH ships. Shipping it would overwrite the
|
|
@@ -110,6 +118,15 @@ ACCEPTED_ABSENT=(
|
|
|
110
118
|
# honest state is "declared, and the declaration records a debt" โ carried to the card.
|
|
111
119
|
"scripts/test_chamber_run_lanes.sh"
|
|
112
120
|
"scripts/test_frontier_digest_retry.sh"
|
|
121
|
+
# ยท test_satellite_publish_gate_lanes.sh / test_satellite_profile_schema_lanes.sh
|
|
122
|
+
# โ 2026-08-20. ๊ฐ์ ๋ชจ์์ด๊ณ , ๋ ๋ค **์ด๋ฏธ files[] ์ ๋ค์ด๊ฐ ์ฑ ๋ฐฐํฌ๋ ๋ค์** ์กํ๋ค.
|
|
123
|
+
# ์ฃผ์ฒด๊ฐ frontier_digest_daily.sh(์๋ ACCEPTED_ABSENT)๋ผ ์๋น์ ๋จธ์ ์ ์ด ๋์์ด ์๋ค.
|
|
124
|
+
# ๐ฅ ์ค์ธก(๋ ์ง์คํธ๋ฆฌ ์ค๋ฌผ 2.5.1, ์๋น์ ์ค์น์์ ์คํ): publish_gate 4 passed / 21 failed,
|
|
125
|
+
# `rc=127`(๋ฌ๋ ๋ถ์ฌ) โ selfcheck ๊ฐ ํ๋ FAIL โ **๋ฐฐํฌ๋ณธ์ด `SELFCHECK: FAIL` ์ด์๋ค.**
|
|
126
|
+
# ๐ฅ ์ด ๊ฒ์ฌ๊ธฐ๋ ๊ทธ๊ฑธ rc=0 ์ผ๋ก ํต๊ณผ์์ผฐ๋ค โ ์ฌ๊ธฐ๋ ใ์ฐธ์กฐ๋ ๊ฒฝ๋ก๊ฐ ์ถํ๋๋ใ๋ฅผ ๋ณด์ง
|
|
127
|
+
# ใ์ถํ๋ ๋ ์ธ์ **์ฃผ์ฒด**๊ฐ ์ถํ๋๋ใ๋ฅผ ์ ๋ณธ๋ค. ๊ทธ ๊ฐญ์ ์์ง ์ ๋ซํ๋ค(๋ช
์ ์์ฌ).
|
|
128
|
+
"scripts/test_satellite_publish_gate_lanes.sh"
|
|
129
|
+
"scripts/test_satellite_profile_schema_lanes.sh"
|
|
113
130
|
"scripts/residency_closure_scan.py"
|
|
114
131
|
"scripts/test_residency_closure_lanes.sh"
|
|
115
132
|
# โโ The two settings destinations, surfaced 2026-08-13 by fixing this file's own extractor โโโโ
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# prior_art_prompt.sh โ T2 ยซ์๋ก ์ง๊ธฐ ์์ยป ์์ ์ **๋ด ์ปจํ
์คํธ๋ก** ํ ์ค์ ๋ฃ๋๋ค.
|
|
3
|
+
#
|
|
4
|
+
# โโ ์ ์ด ์๋ฆฌ์ธ๊ฐ (2026-08-21, ์ฑ๋ฒ ๋ฐ consolidation-path-gate) โโ
|
|
5
|
+
# ์ด์์ ์ค๊ฒฝํ์ด ์ค ๋ ์์ : T1 ์ฐฉ์(์ธ์
๊ฐ ์์) ยท **T2 ์ปจํ
์คํธ๊ฐ ๊ธธ์ด์ง ๋ค ๋ ์๋ก ๋น๋์
ํ ๋**.
|
|
6
|
+
# ์ด ํ
์ด T2 ๋ค. PreToolUse(Write) ๋ ยซ์ ํ์ผ์ ์ฐ๊ธฐ ์ง์ ยป ์ด๋ผ ๊ทธ ๊ฒฝ๊ณ์ ์ ํํ ๊ฒน์น๋ค.
|
|
7
|
+
#
|
|
8
|
+
# โโ ์ ์ฐ๋ฌธ์ด ์๋๋ผ ํ
์ธ๊ฐ (์ค์ธก) โโ
|
|
9
|
+
# ๊ฐ์ ๋ sim: **๋ช
์ ์ง์ 3/3 ยท advisory 0/3 ยท ํ๋ ์ด๋ฐ 0/3**. CLAUDE.md ์ ๊ท์จ์ ๋ ์ ๋ ๊ฒ์
|
|
10
|
+
# ํ๋ ์ด๋ฐ์ด๋ผ 0/3 ์๋ฆฌ๋ค. `additionalContext` ๋ ํ
์ด **๋ชจ๋ธ ์ปจํ
์คํธ์ ์ง์๋ก** ๋ฃ๋ ์ ์ผํ
|
|
11
|
+
# ์ฑ๋์ด๊ณ ([[feedback_advisory_invisible_to_actor]]), ๊ทธ๊ฒ 3/3 ์๋ฆฌ๋ค.
|
|
12
|
+
#
|
|
13
|
+
# โโ ์ ์๋ธ์์ด์ ํธ๊ฐ ์๋๊ฐ โโ
|
|
14
|
+
# ๊ฐ์ ๋ ๋จผ์ ์ง์ `persona-innovator` Mode X ๋ **๋ถ๋ ์ ๋** ๋๋ค. ๋จ์๊ฒ ์ํจ ์๊ณ ๋ ๊ทธ ์๊ฐ์
|
|
15
|
+
# ์ธ์
์ ์ ๋ฐ๊พผ๋ค โ ์ค๋ ์๋ ฅ ์ ๋ฐ 0 ยท ์ธ๋ถ(์ด์์5ยทcodexยทchallengerยท๋ ์ธยทCI) ์ ๋ฐ 10+ ์ด ๊ทธ ์ฆ๊ฑฐ๋ค.
|
|
16
|
+
#
|
|
17
|
+
# โโ ์ด๊ฑด ๋ธ๋ ์ดํฌ๊ฐ ์๋๋ผ ๊ณฑ์
๊ธฐ๋ค (์ด์์, 2026-08-21) โโ
|
|
18
|
+
# *"์ถฉ๋ถํ ์ธ์์ ์ฐพ์๋ดค์ผ๋ฉด ๊ทธ ์ดํ์๋ ๊ฐ์ง ๋ฅ๋ ฅ์ ํ์ฉํด์ ๊ฐ๋ฐ์ ํด๋ด์ผ ํ๋ค. ์ด๋ ๋ด๋ถ์
|
|
19
|
+
# ๋ฅ๋ ฅ์ 100% ํ์ฉํด์ผ์ง. ์กฐํฉํ๋ฉด ๋ฐฐ์๋ ์ง์์ ์ผ๋ก ์ฌ๋ผ๊ฐ ์ ์๊ณ . ๊ทธ๊ฒ ๊ฐ๋ฅํ ์ด์ ๋ ๋ฐ๊นฅ์
|
|
20
|
+
# ๋ฌธ์ ๋ฅผ ํ์ด๋ธ ๋ฐ์ดํฐ๋ค๊ณผ ๋ด๋ถ์ ๋ฅ๋ ฅ ์ค์ผ์คํธ๋ ์ด์
์ด ์๋ก ์ฎ์ด๊ณ ๊ณฑํด์ง๊ธฐ ๋๋ฌธ์ด๋ค."*
|
|
21
|
+
# โ ์ด ํ
์ ๋ชฉ์ ์ ยซ์ง์ง ๋ง๋ผยป ๊ฐ ์๋๋ผ ยซ๋ฐ๊นฅ์ ์
๋ ฅ์ผ๋ก ์ผ์ ์์ 100% ์จ๋ผยป ๋ค.
|
|
22
|
+
# ๊ธ์ง๋ก ์ฝํ๋ฉด ๋ฌด์๋นํ๊ณ , ๊ณฑ์
๊ธฐ๋ก ์ฝํ๋ฉด ์ฐ์ธ๋ค. ๋ฌธ์์ด ๊ทธ๋ ๊ฒ ๋๋์ผ ํ๋ค.
|
|
23
|
+
#
|
|
24
|
+
# โโ ์ ํ๋จ 0์ค โโ
|
|
25
|
+
# ๐ฅ ยซ๋ช
๋ช
์ดํยป ๊ฐ ์ด ํ
์ ์ค์ ๊ธฐ์ ์ด๋ค (2026-08-21 ์ค์ธก). ๊ฐ์ ๋ sim ์์ ์ํ ๋ค ๊ฒ์ํ๋๋ฐ
|
|
26
|
+
# ํํธ ์ฅ ์ชฝ๋ง ์ ๋ต์ ๋ฟ์๋ค(2/3 vs 0/3) โ ์ฐจ์ด๋ ๊ฒ์ ์์ง๊ฐ ์๋๋ผ **๋ฌด์์ ์ฐพ์์ง ์ด๋ฆ ๋ถ์ด๋
|
|
27
|
+
# ๋ฅ๋ ฅ**์ด์๋ค. ์ด์์: *"๋ ์ฑ
์ ์ฝ๋ ์ฌ๋์ด AI ๋ฅผ ๋ ์ ๋ค๋ฃจ์ด๋ธ๋ค."* ๋
์๊ฐ ์ฃผ๋ ๊ฒ์ด ๋ช
๋ช
|
|
28
|
+
# ์ดํ์ด๊ณ , ์ด ํ
์ ๊ทธ๊ฑธ ๊ทธ ์๊ฐ์ ๋น๋ ค์ฃผ๋ ๊ฒ์ด๋ค. ๊ทธ๋์ ๋ฌธ์์ด ยซ์ฐพ์๋ผยป ๊ฐ ์๋๋ผ
|
|
29
|
+
# ยซ์ผ๋ฐ ์ดํ๋ก ์ด๋ฆ ๋ถ์ฌ ์ฐพ์๋ผยป ๋ก ๋๋๋ค.
|
|
30
|
+
#
|
|
31
|
+
# โโ ์ฌ๋ค๋ฆฌ์ง ์ฒดํฌ๋ฆฌ์คํธ๊ฐ ์๋๋ค (์ด์์, 2026-08-21) โโ
|
|
32
|
+
# *"๊ทผ์ฒ์ ์๋ ์ฑ
์ฅ ์ฐพ๊ธฐ > ์ฑ
์ฅ์๋ ์์ผ๋ฉด ๋์๊ด ๊ฐ๊ธฐ"* โ ์์๊ฐ ํ์ค์ด๋ค. ์
์ด ๊ทธ ์์๋ฅผ ์ง์งํ๋ค:
|
|
33
|
+
# ์ฝํผ์ค(โ๋ด๋ถ๋ฏธ์กฐํ 9 > โ์ธ๋ถ๋ฏธ์กฐํ 6) ยท ์ด ์ธ์
sim(๋ด๋ถ ๋ถ์ฌํ์ ์ค๋ฅ 6/6) ยท SAAS 2605.29796
|
|
34
|
+
# (ยซinternal knowledge suffices ์ธ๋ฐ ๋ฌดํฑ๋๊ณ ๊ฒ์ = severe over-searchยป). ํํํ ๋ชฉ๋ก์ ๊ทธ ์์๋ฅผ
|
|
35
|
+
# ๋ชป ์ค๋ค. ๊ทธ๋ฆฌ๊ณ FH ์์ฐ ์ด๋ฆ์ด ์ด๋ฏธ `deep_research_capability_ladder` ๋ค โ ์ฌ๋ค๋ฆฌ๊ฐ ์๋ ํํ๋ค.
|
|
36
|
+
#
|
|
37
|
+
# ๋ด์ฉ์ **์ด๋ฏธ ์๋ ์คํฌ ๋ท์ ๊ฐ๋ฆฌํค๊ธฐ๋ง** ํ๋ค(์ด์์: *"์ฐ๋ฆฌ ์์ ๊ทธ ์คํฌ๋ค์ด ์๊ณ ๊ทธ ์คํฌ๋ค์
|
|
38
|
+
# ๋ฐํํ๋ฉด ์ธ๊ณ์ ๋ฟ๊ฒ ์ง"*). ๋ด๋ถ=asset-placement-gate Step 0.6 ยท CATALOG ยท novelty_claim_check ยท
|
|
39
|
+
# ์ธ๋ถ=deep_research_capability_ladder ยท ์๊ณ =fh-commons:deliberation.
|
|
40
|
+
#
|
|
41
|
+
# โโ degrade โโ
|
|
42
|
+
# ๊ฐ์ญ ํ๋ฉด(ํ์ผ ์ฐ๊ธฐ)์ด๋ผ **์ ๋ ์ ๋ง๋๋ค.** exit 0 ๊ณ ์ , permissionDecision ์์.
|
|
43
|
+
# ํ์ ๋ ์ ํ๋ค โ ํ ์ค ์ ์์ด๋ค. ์กฐ์ฉํ ์คํจ(ํ์ฑ ๋ถ๊ฐยทpython3 ๋ถ์ฌ)๋ ๋ฌด์ ํต๊ณผ.
|
|
44
|
+
set -uo pipefail
|
|
45
|
+
RAW=$(cat 2>/dev/null || true)
|
|
46
|
+
# file_path ์ session_id ๋ฅผ ํ ๋ฒ์ ๋ฝ๋๋ค(ํ์ดํ ๋ ๋ฒ ์ ํ๋ค).
|
|
47
|
+
PARSED=$(printf '%s' "$RAW" | python3 -c '
|
|
48
|
+
import json,sys
|
|
49
|
+
try: d=json.load(sys.stdin)
|
|
50
|
+
except Exception: sys.exit(0)
|
|
51
|
+
ti=d.get("tool_input",{}) or {}
|
|
52
|
+
fp=(ti.get("file_path","") or "")
|
|
53
|
+
cmd=(ti.get("command","") or "").replace("\t"," ").replace("\n"," ")
|
|
54
|
+
sid=(d.get("session_id","") or "")
|
|
55
|
+
sys.stdout.buffer.write((fp+"\t"+sid+"\t"+cmd).encode("utf-8"))
|
|
56
|
+
' 2>/dev/null) || exit 0
|
|
57
|
+
FILE="${PARSED%%$'\t'*}"
|
|
58
|
+
_REST="${PARSED#*$'\t'}"
|
|
59
|
+
SID="${_REST%%$'\t'*}"
|
|
60
|
+
CMD="${_REST#*$'\t'}"
|
|
61
|
+
[ "$CMD" = "$_REST" ] && CMD=""
|
|
62
|
+
|
|
63
|
+
# โโ ๐ฅ Bash ๊ฒฝ๋ก (2026-08-21, ์ฒซ ์ค์ฌ์ฉ์ด ยซ๋น๋ฐํยป ๋ก ๋ฌ๋ค) โโ
|
|
64
|
+
# ์ดํ matcher ๋ `Write` ๋ฟ์ด์๋ค. ๊ทธ๋ฐ๋ฐ auto-mode ๋ ํ์ผ ์์ฑ์ **Bash ์ ๋ฆฌ๋ค์ด๋ ํธ/heredoc**
|
|
65
|
+
# ์ผ๋ก ํ๋ค(์ด ์ ์ฅ์ CLAUDE.md ๊ฐ ๊ทธ๋ ๊ฒ ์ง์ํ๋ค: *"make file changes with sed, heredocsโฆ"*).
|
|
66
|
+
# ์ฆ ์ด ํ
์ด ๊ฒจ๋ฅํ ยซ์ ๋ฉ์ปค๋์ฆ์ ์ง๊ธฐ ์ง์ ยป ์ด **๊ตฌ์กฐ์ ์ผ๋ก Write ๋ฅผ ์ ํ๋ค.**
|
|
67
|
+
# โ matcher ์ Bash ๋ฅผ ๋ฃ๊ณ , `file_path` ๊ฐ ์์ผ๋ฉด **๋ช
๋ น์ ๋ฆฌ๋ค์ด๋ ํธ ๋์**์์ ๋ฝ๋๋ค.
|
|
68
|
+
#
|
|
69
|
+
# ๐ฅ heredoc ์ ํ์ฑํ์ง ์๋๋ค โ ๋ฆฌ๋ค์ด๋ ํธ ยซ๋์ ๊ฒฝ๋กยป ๋ง ๋ณธ๋ค. `cat > x.sh <<EOF` ๋
|
|
70
|
+
# `printf โฆ > x.sh` ๋ ๋์์ `> ` ๋ค์ ์๊ณ , ๋ณธ๋ฌธ์ ๋ณผ ํ์๊ฐ ์๋ค. ๋์น๋ ํํ
|
|
71
|
+
# (`tar x` ยท ์๋ํฐ ยท `cp`)๋ **๋ช
์๋ ์์ฌ**๋ค โ ์ด ํ
์ ํ์ ์ด ์๋๋ผ ์ ์์ด๋ผ ๋ฏธ๋ฐํ์
|
|
72
|
+
# ๋น์ฉ์ด ์ค๋ฐํ๋ณด๋ค ์ธ๋ค.
|
|
73
|
+
if [ -z "$FILE" ] && [ -n "$CMD" ]; then
|
|
74
|
+
# ๐ฅ cross-family 4 HIGH ๋ฅผ ๋ฐ๊ณ ๋ค์ ์ง์๋ค (2026-08-21). ๋ท ๋ค **์ค๋ฐํ**์๊ณ ,
|
|
75
|
+
# ์ด ํ
์์ ์ค๋ฐํ๋ ๋ฏธ๋ฐํ๋ณด๋ค ํจ์ฌ ๋น์ธ๋ค(์์ = ์๊ธฐ๋ฌด๋ ฅํ ๊ฒฝ๋ก).
|
|
76
|
+
# โ `[[:space:]]tee` ์ **๋ค ๊ฒฝ๊ณ๊ฐ ์์ด** `rg tee_probe.py` ๊ฐ tee ๋์์ผ๋ก ์ฝํ๋ค
|
|
77
|
+
# โก `>>?` ๊ฐ **`2>` ์์ `>`** ๋ฅผ ์ก์ `pytest 2> /tmp/errors.sh` ๊ฐ ๋ฐํํ๋ค
|
|
78
|
+
# โข `git diff > /tmp/review_delta.sh` โ ๋ฆฌํฌํธ์ธ๋ฐ ์ด๋ฆ์ด `.sh` ๋ผ ๋ฐํํ๋ค
|
|
79
|
+
# โฃ `"$LOG_DIR/x.sh"` โ ๋ณ์๊ฐ ์ ํ๋ ค ๋ฆฌํฐ๋ด `$LOG_DIR/...` ๋ก ์กด์ฌ๊ฒ์ฌ๋ฅผ ํต๊ณผํ๋ค
|
|
80
|
+
# ์๋ฆฌ ์
: โ ๋ฆฌ๋ค์ด๋ ํธ๋ **์์ด ยซ์ค๋จธ๋ฆฌ ๋๋ ๊ณต๋ฐฑยป** ์ด์ด์ผ ํ๋ค(โ `2>`ยท`1>`ยท`&>` ๋ฐฐ์ )
|
|
81
|
+
# โ `tee` ๋ **์๋ค ๊ณต๋ฐฑ**์ ์๊ตฌํ๋ค โ ๋์์ **์ด ํ๋ก์ ํธ ์**์ด์ด์ผ ํ๋ค
|
|
82
|
+
# โ ๊ฐ โกโข ์ ํ์ค์ ํํ๋ฅผ ํจ๊ป ์ฃฝ์ธ๋ค โ ์ ยซ๋ฉ์ปค๋์ฆยป ์ /tmp ์ ์ ์ฐ๋ค.
|
|
83
|
+
for _c in $(printf '%s' "$CMD" \
|
|
84
|
+
| grep -oE '(^|[[:space:]])(>>?|tee[[:space:]])[[:space:]]*"?'"'"'?[^ "'"'"'|;&)<>]+' 2>/dev/null \
|
|
85
|
+
| sed -E 's/^[[:space:]]*(>>?|tee[[:space:]])[[:space:]]*//; s/^["'"'"']//'); do
|
|
86
|
+
case "$_c" in
|
|
87
|
+
''|/dev/*|\&*|-) continue ;;
|
|
88
|
+
*'$'*) continue ;; # โฃ ๋ฏธํ์ฅ ๋ณ์ โ ๋ฆฌํฐ๋ด๋ก ํ์ ํ๋ฉด ๊ฑฐ์ง์ด๋ค
|
|
89
|
+
*..*) continue ;; # ์์ ํ์ถ ๊ฒฝ๋ก๋ ยซ์ด ํ๋ก์ ํธ ์ยป ์ด ์๋๋ค
|
|
90
|
+
esac
|
|
91
|
+
# โ ํ๋ก์ ํธ ๋ฐ ์ ๋๊ฒฝ๋ก๋ ๋์์ด ์๋๋ค. ์๋๊ฒฝ๋ก๋ ํ๋ก์ ํธ ๊ธฐ์ค์ด๋ผ ํต๊ณผ์ํจ๋ค.
|
|
92
|
+
case "$_c" in
|
|
93
|
+
/*) case "$_c" in
|
|
94
|
+
"${CLAUDE_PROJECT_DIR:-/nonexistent-project-dir}"/*) ;;
|
|
95
|
+
*) continue ;;
|
|
96
|
+
esac ;;
|
|
97
|
+
esac
|
|
98
|
+
case "$_c" in
|
|
99
|
+
*.sh|*/SKILL.md|*/SKILL_detail.md|*/agents/*.md|*/rules/*.md|*.py|*.js|*.ts) FILE="$_c"; break ;;
|
|
100
|
+
esac
|
|
101
|
+
done
|
|
102
|
+
fi
|
|
103
|
+
[ -n "$FILE" ] || exit 0
|
|
104
|
+
|
|
105
|
+
# โโ ๋ฐ๋ ์กฐ๊ฑด โ ์์์ด ์ด ๋ฅ๋ ฅ์ ์ฃฝ์ธ๋ค(๋๋
์ ยซ์๊ธฐ๋ฌด๋ ฅํ ๊ฒฝ๋กยป) โโ
|
|
106
|
+
# โ ์ด๋ฏธ ์๋ ํ์ผ ํธ์ง์ ยซ์๋ก ์ง๊ธฐยป ๊ฐ ์๋๋ค
|
|
107
|
+
[ -e "$FILE" ] && exit 0
|
|
108
|
+
# โก ๊ธฐ๋กยท์ฐ์ถ๋ฌผ์ ๋์์ด ์๋๋ค. ยซ๋ฉ์ปค๋์ฆยป ๋ง.
|
|
109
|
+
# ๐ฅ ์๋๊ฒฝ๋ก๋ ์ก์์ผ ํ๋ค (2026-08-21 ๋ ์ธ D4). ์ดํ ์ ์ธ๋ `*/tracks/*` ๋ฟ์ด๋ผ
|
|
110
|
+
# **์ ๋๊ฒฝ๋ก๋ง** ๊ฑธ๋ ๋ค โ Write ๋ ์ ๋๊ฒฝ๋ก๋ฅผ ์ฃผ์ง๋ง **Bash ๋ ์๋๊ฒฝ๋ก๋ฅผ ์ค๋ค**
|
|
111
|
+
# (`cat > tracks/_meta/note.sh`). Bash ํ์ ๋ถ์ด๋ฉด์ ์ด ๋น๋์นญ์ด ๋๋ฌ๋ฌ๊ณ ,
|
|
112
|
+
# ์ ์ธ๊ฐ ์ ๋จน์ผ๋ฉด ์ด ํ
์ ์ฐ์ถ๋ฌผ๋ง๋ค ๋จ๋ ์์์ด ๋๋ค(=์๊ธฐ๋ฌด๋ ฅํ ๊ฒฝ๋ก).
|
|
113
|
+
case "$FILE" in
|
|
114
|
+
tracks/*|logs/*|.git/*|node_modules/*) exit 0 ;;
|
|
115
|
+
*/tracks/*|*/logs/*|*.log|*.json|*.lock|*/node_modules/*|*/.git/*) exit 0 ;;
|
|
116
|
+
esac
|
|
117
|
+
case "$FILE" in
|
|
118
|
+
*.sh|*/SKILL.md|*/SKILL_detail.md|*/agents/*.md|*/rules/*.md|*.py|*.js|*.ts) ;;
|
|
119
|
+
*) exit 0 ;;
|
|
120
|
+
esac
|
|
121
|
+
# โข **์ธ์
๋น** ํ ๋ฒ. ๋งค Write ๋ง๋ค ๋จ๋ฉด 3ํ ๊ฑฐ์ ๋ก ์กฐ์ฉํ ์ฃฝ๋๋ค.
|
|
122
|
+
# ๐ฅ ์ดํ์ ์คํฌํ๋ฅผ **๋ ์ง**๋ก ์ก์๋ค โ ์ฃผ์์ ยซ์ธ์
๋นยป ์ธ๋ฐ ๊ตฌํ์ ยซํ๋ฃจ๋นยป ์ด์๊ณ , ํ๋ฃจ์ ์ธ์
์
|
|
123
|
+
# ์ฌ๋ฟ ๋๋ ํ๊ฒฝ(์ด ์ด์์๊ฐ ๊ทธ๋ ๋ค)์์๋ **์ฒซ ์ธ์
๋ง ๋จ๊ณ ๋๋จธ์ง๋ ์ ๋ถ ์นจ๋ฌต**ํ๋ค. ๋ด ๋ ์ธ๋
|
|
124
|
+
# ๋ชป ์ก์๋ค: ๋๋ ํฐ๋ฆฌ๋ง ๊ฐ๋์ง ๋ ์ง ์๋ฏธ๋ก ์ ์ ์๋ค. ํ
์
๋ ฅ์ `session_id` ๋ก ๊ณ ์น๋ค.
|
|
125
|
+
# session_id ๊ฐ ์์ผ๋ฉด(๊ตฌ๋ฒ์ ยท์๋ ํธ์ถ) ๋ ์ง๋ก degrade โ ์นจ๋ฌต์ด ์๋๋ผ ยซ๋ ์์ฃผยป ์ชฝ์ด๋ค.
|
|
126
|
+
# ๐ฅ `session_id` ๋ **์ ๋ขฐํ ์ ์๋ ์
๋ ฅ**์ด๋ค (cross-family HIGH#5). ์ค์ธก: `x/../../../name`
|
|
127
|
+
# ์ด ์ ๋ ๊ฒ์ฌ๋ฅผ ํต๊ณผํด ํ๋ก์ ํธ ๋ฐ์ ํ์ผ์ ๋ง๋ค์๊ณ , ๋์ `/` ๋ฅผ ๋ถ์ด๋ฉด ์คํฌํ๊ฐ ์์
|
|
128
|
+
# ์ ์จ์ ธ **๋งค๋ฒ ๋ฐํ**ํ๋ค(์ธ์
๋น 1ํ๊ฐ ๋ฌด๋ ฅํ). ํ์ดํธ๋ฆฌ์คํธ๋ก ์ขํ๋ค โ ๋น ๊ฐ์ด ๋๋ฉด
|
|
129
|
+
# ๋ ์ง๋ก degrade(์นจ๋ฌต์ด ์๋๋ผ ยซ๋ ์์ฃผยป ์ชฝ).
|
|
130
|
+
_SID_SAFE=$(printf '%s' "${SID:-}" | tr -cd 'A-Za-z0-9_-')
|
|
131
|
+
STAMP="${CLAUDE_PROJECT_DIR:-.}/.claude/.prior_art_prompted_${_SID_SAFE:-$(date +%Y%m%d)}"
|
|
132
|
+
[ -f "$STAMP" ] && exit 0
|
|
133
|
+
mkdir -p "$(dirname "$STAMP")" 2>/dev/null || true
|
|
134
|
+
: > "$STAMP" 2>/dev/null || true
|
|
135
|
+
|
|
136
|
+
MSG="๐ ์ ๋ฉ์ปค๋์ฆ์ ์ฐ๋ ค๋ ์ฐธ์ด๋ค ($(basename "$FILE")). ์ง๊ธฐ ์ ์ **์ฑ
์ฅ๋ถํฐ, ์์ผ๋ฉด ๋์๊ด** โ
|
|
137
|
+
์ ํ์ผ๋ฉด ์ง๊ธ ํ๊ณ , ํ์ผ๋ฉด ์ด ์ค์ ๋ฌด์ํด๋ผ.
|
|
138
|
+
|
|
139
|
+
โ ์ฑ
์ฅ ๋ฐ๋ก ์์ ์๋. **์ฌ๊ธฐ๊ฐ ๋๋ถ๋ถ์ด๋ค.**
|
|
140
|
+
์ฐ๋ฆฌ ์ : CATALOG.md ยท .claude/registry/ ยท plugins/**/SKILL.md ยท scripts/ ๋ฅผ ์ค์ ๋ก grep
|
|
141
|
+
์ ์นธ : LOCAL_SKILL_REGISTRY ยท scripts/cluster_capability_scan.sh ยท ยงCross-Project Skill Bus
|
|
142
|
+
๐ฅ ์ฌ๊ธฐ์ ๋์ค๋ฉด ยซ์ฐพ์์จ ๊ฒยป ์ด ์๋๋ผ **๋ฐ๋ก ํธ์ถ ๊ฐ๋ฅํ ๋ฅ๋ ฅ**์ด๋ค
|
|
143
|
+
๐ฅ ยซgrep 0๊ฑดยป ์ ยซ์๋คยป ๋ก ํ์ ํ์ง ๋ง๋ผ โ ์ค๋ ์ค์ธก์์ ์ธ์
6/6 ์ด ์ด ์๋ฆฌ์์ ํ๋ ธ๋ค.
|
|
144
|
+
๋ด ์ดํ๋ก๋ง ๋๋๋ ธ์ ๊ฐ๋ฅ์ฑ์ ๋จผ์ ์์ฌํด๋ผ.
|
|
145
|
+
|
|
146
|
+
โก ๋์๊ด **์ฑ
์ฅ์ ์์ ๋๋ง** ๊ฐ๋ค. ์๋๋ฐ ๊ฐ๋ฉด ๊ทธ๊ฑด over-search ๋ค(arXiv 2605.29796 ์ด ๋ช
๋ช
ํ ์คํจ๋ชจ๋).
|
|
147
|
+
๊ฐ ๋๋ ๋ฌธ์ ๋ฅผ **์ผ๋ฐ ์ดํ๋ก ์ด๋ฆ ๋ถ์ฌ์** โ ๋ด๋ถ ์ดํ๋ก๋ ์ธ๊ณ์ ์ ๋ฟ๋๋ค.
|
|
148
|
+
๊ฒฝ๋ก: knowledge/shared/harness-core/deep_research_capability_ladder.md
|
|
149
|
+
|
|
150
|
+
โข ์๊ณ โ โก๋ฅผ ๋๊ณ ๊ฐ๋๋ โ fh-commons:deliberation (์ ์โ๋ฐ๋ก โ์ข
ํฉ)
|
|
151
|
+
|
|
152
|
+
โฃ ์ง๊ธฐ ๐ฅ **ํ์์ ๋์ด ์๋๋ผ ์
๋ ฅ์ด๋ค.** ์ฐพ์์ผ๋ฉด ๊ฑฐ๊ธฐ์ ๋ฉ์ถ์ง ๋ง๊ณ **๊ฐ์ง ๋ฅ๋ ฅ์ 100% ์จ์
|
|
153
|
+
์ง์ด๋ผ** โ ๋ฐ๊นฅ์ด ํผ ๋ฐ์ดํฐ ร ๋ด๋ถ ์ค์ผ์คํธ๋ ์ด์
์ ๋ํด์ง๋ ๊ฒ ์๋๋ผ **๊ณฑํด์ง๋ค**
|
|
154
|
+
(arXiv 2607.06906 ์ค์ธก: ํ์ง/๋ฌ๋ฌ +82%, ๋ฐฑ๋งํ ํฐ๋น ์์ 54.9โ92.0).
|
|
155
|
+
ํฉ์ฃผ ํ์์ ์ด๋ฏธ ์๋ค: CLAUDE.md ยงIntent Marshaling.
|
|
156
|
+
|
|
157
|
+
์ ๋ฐ์ ์นธ์ด ์์ผ๋ฉด ํ ์ค๋ก ๋ฌผ์ด๋ผ: ยซ์ด ๋ฌธ์ ์ด๋ฆ์ด X ๊ฐ์๋ฐ ํ์ธํด๋ณผ๊น?ยป
|
|
158
|
+
ํ์ ์ด ์๋๋ผ ์ ์์ด๋ค. ์ด๋ฏธ ๋ฐ์์ผ๋ฉด ์งํํด๋ผ โ ๋จ, ์งํํ ๋ **๋ด๋ถ ๋ฅ๋ ฅ์ ๋ ์ฐ์ง ๋ง๋ผ.**"
|
|
159
|
+
|
|
160
|
+
if json_out=$(printf '%s' "$MSG" | PYTHONIOENCODING=utf-8 python3 -c '
|
|
161
|
+
import json,sys
|
|
162
|
+
h=sys.stdin.read()
|
|
163
|
+
print(json.dumps({"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":h}}))
|
|
164
|
+
' 2>/dev/null) && [ -n "$json_out" ]; then
|
|
165
|
+
printf '%s\n' "$json_out"; exit 0
|
|
166
|
+
fi
|
|
167
|
+
printf '%s\n' "$MSG" >&2
|
|
168
|
+
exit 0
|
package/scripts/psa_scan_lib.sh
CHANGED
|
@@ -112,7 +112,19 @@ psa_low_allowlisted() {
|
|
|
112
112
|
# Prints diagnostics for every degraded state; sets the flags above. NEVER exits โ the caller owns
|
|
113
113
|
# the degrade direction, because it differs by surface (see the header).
|
|
114
114
|
psa_load() {
|
|
115
|
-
local defaults="$1" override="$2" raw ov
|
|
115
|
+
local defaults="$1" override="$2" raw ov _tabtest
|
|
116
|
+
# ๐ฅ R5-3: ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ `$'\t'` (ANSI-C quoting) ์ ์์กดํ๋ค โ **POSIX ๊ฐ ์๋๋ค.**
|
|
117
|
+
# dash ์์๋ ๋ฆฌํฐ๋ด `$\t` ๋ก ์ฝํ ์ ์ ํ์ด ยซTAB ์์ยป ์ผ๋ก ๋จ์ด์ง๊ณ , ์คํธ๋ฆผ์ด ํต์งธ๋ก
|
|
118
|
+
# ๋น๋ฉด์ **bad=1 ยท stream=[] ๋ก ์กฐ์ฉํ ๋ฌด๋ ฅํ**๋๋ค(cross-family ์ค์ธก). ๊ทธ๊ฑด ๋ฏธ์ธก์ ์ด์ง
|
|
119
|
+
# ใํจํด์ด ์๋คใ๊ฐ ์๋๋ค. ์ง์ ๋ฒ์๋ bash/zsh ์ด๊ณ , ๊ทธ๊ฑธ ์ฐ๋ฌธ์ด ์๋๋ผ ์ฌ๊ธฐ์ ํ์ธํ๋ค.
|
|
120
|
+
# ํ๋ณ์ ๊ธธ์ด๋ค โ ์ค์ธก: bash/zsh `len=1`(์ง์ง TAB) ยท dash `len=3`(๋ฆฌํฐ๋ด `$\t`).
|
|
121
|
+
_tabtest=$'\t'
|
|
122
|
+
if [ "${#_tabtest}" -ne 1 ]; then
|
|
123
|
+
echo " โ INSTRUMENT DEAD โ this shell does not support \$'\\t' (ANSI-C quoting)." >&2
|
|
124
|
+
echo " psa_scan_lib.sh requires bash or zsh. Re-run under one of them; NOT SCANNED." >&2
|
|
125
|
+
PSA_STREAM=""; PSA_DEFAULTS_OK=0; PSA_OVERRIDE_PRESENT=0; PSA_BAD_ROWS=1
|
|
126
|
+
return 3
|
|
127
|
+
fi
|
|
116
128
|
PSA_STREAM=""; PSA_DEFAULTS_OK=0; PSA_OVERRIDE_PRESENT=0; PSA_BAD_ROWS=0
|
|
117
129
|
|
|
118
130
|
# Layer 1 โ committed defaults. This file ships with the repo, so missing/unreadable/empty is a
|
|
@@ -156,11 +168,23 @@ $ov"; PSA_OVERRIDE_PRESENT=1
|
|
|
156
168
|
echo " โ unusable pattern row (no TAB between severity and regex): ${line%%$'\t'*}"
|
|
157
169
|
PSA_BAD_ROWS=$((PSA_BAD_ROWS+1)); continue
|
|
158
170
|
fi
|
|
159
|
-
|
|
171
|
+
# ๐ฅ R4-1: ์ฌ๊ธฐ๋ `cmd; rc=$?` ์๋ค โ P3 ๋ฅผ ์ค์บ ๊ฒฝ๋ก์์๋ง ๊ณ ์น๊ณ **๋ก๋ ๊ฒฝ๋ก๋ก ์ ํ๋ฅผ ์ ๋ดค๋ค**.
|
|
172
|
+
# ์ ์ ํจํด์ ๋น ์
๋ ฅ์ rc=1 ์ ๋ด๋ฏ๋ก `set -e` ํธ์ถ์๋ psa_load ์์ ์ฃฝ๋๋ค(์ ์ฌ, ์ ์
ธ ์ค์ธก).
|
|
173
|
+
if printf '' | grep -E "$re" >/dev/null 2>&1; then rc=0; else rc=$?; fi # >=2 = regex error
|
|
160
174
|
if [ "$rc" -ge 2 ]; then
|
|
161
175
|
echo " โ unusable pattern (invalid regex) โ this detector would match nothing: [${line%%$'\t'*}]"
|
|
162
176
|
PSA_BAD_ROWS=$((PSA_BAD_ROWS+1)); continue
|
|
163
177
|
fi
|
|
178
|
+
# ๐ฅ P4 (์ ์ฌ ๊ฒฐํจ): ํ๋ณ๋ ฅ ์๋ ํจํด์ ๊ฑฐ๋ถํ๋ค โ ๋น ํจํด ยท `^` ยท `a*` ยท `SECRET|^$`.
|
|
179
|
+
# ๋ชจ๋ ์
๋ ฅ์ ๋งค์นํ๋ฏ๋ก ๊ฒ์ถ๊ธฐ๋ก **์ธ ์ ์๋๋ฐ** ์ดํ ๋ก๋๋ ์ ํจ๋ก ๋ฐ์๋ค์๋ค.
|
|
180
|
+
# ๐ฅ ๊ทธ๋ฆฌ๊ณ ์ด ๊ฒ์ฌ๋ **์์ ์ปดํ์ผ ๊ฒ์ฌ๋ก๋ ๋ชป ํ๋ค**: `printf ''` ๋ ์ค์ 0๊ฐ ์ฃผ๋ฏ๋ก
|
|
181
|
+
# grep ์ด ๋งค์นํ ๋์ ์์ฒด๊ฐ ์์ด **์ด๋ค ํจํด์ด๋ rc=1** ์ด๋ค(์ค์ธก). ์ ์ฃผ์์
|
|
182
|
+
# ใ0 = ์ปดํ์ผ๋จใ์ ๋๋ฌ ๋ถ๊ฐ๋ฅํ ๊ฐ์ ์์ ํ ๊ฒ์ด์๋ค. ๋น ยซ์คยป ์ ๋ฐ๋ก ๋จน์ธ๋ค.
|
|
183
|
+
if printf '\n' | grep -E "$re" >/dev/null 2>&1; then rc=0; else rc=$?; fi
|
|
184
|
+
if [ "$rc" -eq 0 ]; then
|
|
185
|
+
echo " โ unusable pattern (matches the EMPTY string โ no discrimination): [${line%%$'\t'*}]"
|
|
186
|
+
PSA_BAD_ROWS=$((PSA_BAD_ROWS+1)); continue
|
|
187
|
+
fi
|
|
164
188
|
valid="$valid
|
|
165
189
|
$line"
|
|
166
190
|
done <<PSA_ROWS
|
|
@@ -208,31 +232,128 @@ psa_detect_operator_context() {
|
|
|
208
232
|
# Why path-tagged input: the LOW allowlist is per FILE, so a scanner that flattens content loses the
|
|
209
233
|
# only information needed to apply it. The push copy did exactly that and blocked its own first real
|
|
210
234
|
# push on a companion-store name inside the script whose job is to sync to the companion store.
|
|
235
|
+
# _psa_in_selftest โ ยซ์ง๊ธ psa_require_live ์์์ ๋ถ๋ฆฐ ๊ฒ์ธ๊ฐยป๋ฅผ **ํธ์ถ ์คํ**์ผ๋ก ํ๋ณํ๋ค.
|
|
236
|
+
# ๐ฅ ๋ณ์ ํ์์ ์ฐ๋ฉด ์ ๋๋ค: ์ธ๋ถ๊ฐ `_PSA_IN_SELFTEST=1` ์ ๋ฏธ๋ฆฌ export ํ๋ ๊ฒ๋ง์ผ๋ก
|
|
237
|
+
# ์๊ฐ๊ฒ์ฌ๊ฐ ํต์งธ๋ก ์๋ต๋๋ค(cross-family ๋ฐ๋ก 1b, ์คํ ์ฌํ). ์ ์ญ ์ํ๋ฅผ ๊ถํ ํ์์ฒ๋ผ
|
|
238
|
+
# ๋ฏฟ๋ ํํ๋ค. ํธ์ถ ์คํ์ **ํธ์ถ์๊ฐ ๋ฏธ๋ฆฌ ์ฌ์ ์ ์๋ค.**
|
|
239
|
+
# bash=FUNCNAME ยท zsh=funcstack. ๋ ๋ค ์์ผ๋ฉด ํ๋ณ ๋ถ๊ฐ์ด๋ฏ๋ก **์ฌ์ง์
์ด ์๋๋ผ๊ณ ๋ตํ๋ค** โ
|
|
240
|
+
# ๊ทธ๋ฌ๋ฉด ์๊ฐ๊ฒ์ฌ๊ฐ ํ ๋ฒ ๋ ๋ ๋ฟ(๋ฌดํ์ฌ๊ท๋ psa_require_live ๊ฐ ์ด ํจ์๋ฅผ ํ ๊ฒน๋ง ๋ถ๋ฅด๋ฏ๋ก
|
|
241
|
+
# ๋ฐ์ํ์ง ์๋๋ค), ๋ฐ๋๋ก ยซ์ฌ์ง์
์ด๋คใ๋ก ์ ์ผ๋ฉด ๊ทธ๊ฒ fail-open ์ด๋ค.
|
|
242
|
+
_psa_in_selftest() {
|
|
243
|
+
local _st=""
|
|
244
|
+
# shellcheck disable=SC2154
|
|
245
|
+
if [ -n "${FUNCNAME[*]:-}" ]; then _st=" ${FUNCNAME[*]} "
|
|
246
|
+
elif [ -n "${funcstack[*]:-}" ]; then _st=" ${funcstack[*]} "
|
|
247
|
+
else return 1; fi
|
|
248
|
+
case "$_st" in *" psa_require_live "*) return 0 ;; esac
|
|
249
|
+
return 1
|
|
250
|
+
}
|
|
251
|
+
|
|
211
252
|
psa_scan_tagged() {
|
|
212
|
-
|
|
213
|
-
|
|
253
|
+
# ๐ฅ `path` ๋ฅผ ์ง์ญ์ ์ธํ์ง ๋ง๋ผ โ zsh ์์ `path` ๋ `PATH` ์ **tied ๋ ํน์ ๋ฐฐ์ด**์ด๋ผ
|
|
254
|
+
# `local path` ํ๋ ์๊ฐ ๊ทธ ์ค์ฝํ์ `PATH` ๊ฐ ๋น ๋ฌธ์์ด์ด ๋๊ณ , ๋ฐ๋ก ๋ค์ ์ค์
|
|
255
|
+
# `$(cat)` ๋ถํฐ `command not found: cat` ์ผ๋ก ์ฃฝ๋๋ค. ๊ทธ๋ฐ๋ฐ ์ด ํจ์์ ๊ณ์ฝ์ด
|
|
256
|
+
# ใ๋น ์
๋ ฅ = ์ ๊ณ ํ ๊ฒ ์์ = return 0ใ์ด๋ผ **๊ณ๊ธฐ ์ฌ๋ง์ด ยซ๊นจ๋ํ ์ค์บยป๊ณผ ๋ฐ์ดํธ ๋จ์๋ก
|
|
257
|
+
# ๊ฐ์ ์ถ๋ ฅ**์ด ๋๋ค โ ์ด ์ ์ฅ์๊ฐ ์ด๋ฆ ๋ถ์ธ ยซ๋ฏธ์ธก์ ์ 0์ผ๋ก ๋ ๋ยป ์ ๊ฐ์ฅ ์กฐ์ฉํ ์ผ๊ตด์ด๋ค.
|
|
258
|
+
# ์ค์ธก 2026-08-21: zsh ์์ known-positiveยทknown-negative ๊ฐ **๋ ๋ค rc=0** ์ด์๋ค.
|
|
259
|
+
# ํ์ ํน์๋ณ์(`fpath` `status` `argv` `cdpath` โฆ)๋ ๊ฐ์ ํจ์ ์ด๋ค.
|
|
260
|
+
local input hit=0 row sev re _psa_path body tok _line_hits _tok_hits _lg _tg
|
|
261
|
+
|
|
262
|
+
# โโ E: ์ง์
์์กด์ฑ ๊ฐ๋ (2026-08-21, cross-family FAIL ํ์ ํ ์ฌ์์ฑ) โโโโโโโโโโโโโ
|
|
263
|
+
# ๐ฅ **์ดํ E ๋ fail-open ์ ๋ชป ๋ซ์๋ค.** codex/gpt-5.6-sol ์ด ๋ฐ๋ก 6๊ฐ๋ฅผ ์ ๋ถ ์คํ์ผ๋ก
|
|
264
|
+
# ์ฌํํ๊ณ ํ์ ์ FAIL ์ด์๋ค. ์ดํ์ ๊ตฌ์กฐ์ ๋งน์ : **ยซ์๊ฐ๊ฒ์ฌ๊ฐ ํ ๋ฒ ํต๊ณผํ๋คยป๋ฅผ
|
|
265
|
+
# ยซ๊ทธ ๋ค์ ์ค์ ์ค์บ๋ ์คํ๋๋คยป๋ก ํ์ฅ**ํ ๊ฒ. ๊ทธ ์ฌ์ด์ ์๊ฐยท์ ์ญ์ํยทํ์ดํ๋ผ์ธ
|
|
266
|
+
# ์ํ๋ผ๋ ๋จ์ ์ ์ด ์
์๋ค. ์ฌ์์ฑ์ด ๋ซ๋ ๊ฒ๊ณผ ๋ชป ๋ซ๋ ๊ฒ์ ์๋์ ์ด๋ฆ์ผ๋ก ์ ๋๋ค.
|
|
267
|
+
#
|
|
268
|
+
# ๐ฅ **์บ์ ์ ๊ฑฐ.** ์ดํ์ `_PSA_LIVE_OK` ๋ก ํ๋ก์ธ์ค๋น 1ํ๋ง ๊ฒ์ฌํ๋ค โ ์ฒซ ์์ฑ ๋ค
|
|
269
|
+
# grep ์ ์ฃฝ์ด๋ฉด ๊ฐ์ ์์ฑ์ด rc=0 ์ด ๋๋ค(codex ์ฌํ). ๊ทธ๋ฆฌ๊ณ **์บ์๋ ๋ซ๋ฆฐ ์ชฝ(๊ธด
|
|
270
|
+
# ์คํฌ ์ธ์
)์์๋ง ์ํํ๊ณ ์์ ํ ์ชฝ(์งง์ ํ
)์์๋ง ์ด๋**์ด๋ผ ๋ฐฉํฅ์ด ๋ฐ๋์๋ค.
|
|
271
|
+
# ๋น์ฉ์ **์ค์ธกํ๋ค: psa_require_live 1ํ โ 64ms** (ํ
๊ฒฝ๋ก ์ต๋ 4ํ = 0.26์ด).
|
|
272
|
+
# ์ดํ์ ์ด ๋น์ฉ์ **์ ์ฌ๊ณ ** ์บ์๋ฅผ ์ ๋นํํ๋ค โ ๊ทธ๊ฒ ์ง์ง ๊ฒฐํจ์ด์๋ค.
|
|
273
|
+
#
|
|
274
|
+
# ๐ฅ **์ฌ์ง์
ํ์์ `local` ๋ก ยซ๋์ ์ค์ฝํยป์ ๋๋ค.** ์ดํ์ ์ ์ญ ๋ณ์๋ฅผ ๊ถํ ํ์์ฒ๋ผ
|
|
275
|
+
# ๋ฏฟ์ด์, ์ธ๋ถ๊ฐ `_PSA_IN_SELFTEST=1` ์ ๋ฏธ๋ฆฌ export ํ๋ฉด ์๊ฐ๊ฒ์ฌ๊ฐ **ํต์งธ๋ก ์๋ต**๋๋ค
|
|
276
|
+
# (codex ์ฌํ: `grep(){ return 127; }` ์ํ์์ known-positive ๊ฐ rc=0 ๋ฌด์ถ๋ ฅ).
|
|
277
|
+
# `local` ์ ์ธ์ ์ธ๋ถ ๊ฐ์ **๊ฐ๋ฆฌ๊ณ **, ํจ์๊ฐ ๋๋๋ฉด **์๋ ๋ณต๊ตฌ**๋๋ค โ unset ์คํจ๋
|
|
278
|
+
# ์๊ทธ๋ยทํธ๋ฉ์ผ๋ก ๊ฐ์ด ๋จ๋ ๊ฒฝ๋ก๊ฐ ์์ด์ง๋ค.
|
|
279
|
+
if ! _psa_in_selftest; then
|
|
280
|
+
# ๐ฅ ์ค์ ํจํด ์คํธ๋ฆผ์ ์ฌ๊ธฐ์ ๋ณธ๋ค. ์๊ฐ๊ฒ์ฌ๋ PSA_STREAM ์ ์นด๋๋ฆฌ์๋ก **๋ฐ๊ฟ์น๊ธฐ**
|
|
281
|
+
# ํ๋ฏ๋ก ยซ์ง์ง ํจํด์ด ๋น์๊ฑฐ๋ ๊นจ์ก๋์งยป๋ฅผ ๊ตฌ์กฐ์ ์ผ๋ก ๋ชป ๋ณธ๋ค(codex ๋ฐ๋ก 3).
|
|
282
|
+
if [ -z "${PSA_STREAM:-}" ]; then
|
|
283
|
+
echo " โ INSTRUMENT DEAD โ PSA_STREAM is empty; nothing to match against. NOT SCANNED."
|
|
284
|
+
echo " โ INSTRUMENT DEAD โ PSA_STREAM is empty; NOT SCANNED (call psa_load first)." >&2
|
|
285
|
+
return 3
|
|
286
|
+
fi
|
|
287
|
+
# ๐ฅ R4-6: **๋ถ๋ถ ๋ก๋๋ ํจํด ์งํฉ์ผ๋ก ยซ๊นจ๋ยป์ ๋งํ์ง ์๋๋ค.** ์ดํ์ ์ด๊ฒ์ ยซํธ์ถ์
|
|
288
|
+
# ๊ณ์ฝยป์ผ๋ก ๋์๊ณ (ํค๋๊ฐ ๊ทธ๋ ๊ฒ ์ ์ด๋๋ค), cross-family ๊ฐ ๊ทธ ๊ณ์ฝ์ ์ ์งํค๋ ํธ์ถ์๋ฅผ
|
|
289
|
+
# ์ค์ธก ์ฌํํ๋ค โ `SECRET|^$` ํ ํ์ด ๋จ์ด์ ธ ๋๊ฐ ์งํฉ์ผ๋ก `SECRET` ์
๋ ฅ์ด rc=0 ยซ๊นจ๋ยป.
|
|
290
|
+
# ์ฐ๋ฌธ ๊ณ์ฝ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ธต์ผ๋ก ๋ด๋ฆฐ๋ค: ์ ์ค๋ฆฐ ๊ฒ์ถ๊ธฐ๊ฐ ์์ผ๋ฉด ๊ทธ๊ฑด ๋ฏธ์ธก์ ์ด๋ค.
|
|
291
|
+
# (psa_load ๋ฅผ ์ ๋ถ๋ฅธ ํธ์ถ์๋ PSA_BAD_ROWS ๊ฐ ๋ฏธ์ค์ ์ด๊ณ , ๊ทธ ๊ฒฝ๋ก๋ ์ ๋น-์คํธ๋ฆผ
|
|
292
|
+
# ๊ฒ์ฌ๊ฐ ์ด๋ฏธ ์ก๋๋ค. `case` ๋ก ๋ณด๋ ์ด์ ๋ ๋น์์น ์ค์ผ์์ fail-open ํ์ง ์๊ธฐ ์ํด์๋ค.)
|
|
293
|
+
case "${PSA_BAD_ROWS:-0}" in
|
|
294
|
+
0) ;;
|
|
295
|
+
*) echo " โ INSTRUMENT DEAD โ ${PSA_BAD_ROWS} pattern row(s) were dropped; the detector set is PARTIAL. NOT SCANNED."
|
|
296
|
+
echo " โ INSTRUMENT DEAD โ ${PSA_BAD_ROWS} pattern row(s) dropped โ a partial set cannot report clean." >&2
|
|
297
|
+
return 3 ;;
|
|
298
|
+
esac
|
|
299
|
+
if ! psa_require_live >/dev/null 2>&1; then
|
|
300
|
+
echo " โ INSTRUMENT DEAD โ psa_scan_tagged self-test failed. NOT SCANNED."
|
|
301
|
+
echo " โ INSTRUMENT DEAD โ psa_scan_tagged self-test failed. NOT SCANNED." >&2
|
|
302
|
+
echo " A 0 from this scanner is UNMEASURED, not clean. Re-run under bash." >&2
|
|
303
|
+
return 3
|
|
304
|
+
fi
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
# ๐ฅ `cat` ์ ์ํ๋ฅผ ๋ฒ๋ฆฌ์ง ๋ง๋ผ (codex ๋ฐ๋ก 1). ์ดํ์ `input=$(cat)` ๋ก ๋์
๋ง ํ๊ณ
|
|
308
|
+
# rc ๋ฅผ ๋ฒ๋ ค์, cat ์ด ์ฃฝ์ด ์
๋ ฅ์ด ๋น๋ฉด ๊ทธ๊ฒ ๊ณง ยซ์ ๊ณ ํ ๊ฒ ์์ยป์ด ๋๋ค.
|
|
309
|
+
if ! input=$(cat); then
|
|
310
|
+
echo " โ INSTRUMENT DEAD โ could not read stdin. NOT SCANNED."
|
|
311
|
+
echo " โ INSTRUMENT DEAD โ could not read stdin. NOT SCANNED." >&2
|
|
312
|
+
return 3
|
|
313
|
+
fi
|
|
214
314
|
[ -n "$input" ] || return 0
|
|
215
315
|
while IFS= read -r row; do
|
|
216
316
|
case "$row" in ''|\#*) continue;; esac
|
|
217
317
|
re="${row#*$'\t'}"; [ "$re" = "$row" ] && continue
|
|
218
318
|
sev="${row%%$'\t'*}"
|
|
319
|
+
# ๐ฅ grep ์ํ๋ฅผ ยซ๋งค์น ์์ยป ์ผ๋ก ์ ์ง ๋ง๋ผ (cross-family ๋ฐ๋ก 2, 2026-08-21).
|
|
320
|
+
# `2>/dev/null || true` ๋ rc 0(๋งค์น)ยท1(์์)ยท**2 ์ด์(๊ณ๊ธฐ ์ค๋ฅ)**์ ํ๋๋ก ๋ญ๊ฐ ๋ค.
|
|
321
|
+
# ์ค์ธก ๋๋ฌ ๊ฒฝ๋ก: ํจํด ํ์ผ์ **๊นจ์ง ์ ๊ท์**์ด ํ ์ค ์์ผ๋ฉด ๊ทธ ํ๋ง ์กฐ์ฉํ ์๋ฌด๊ฒ๋
|
|
322
|
+
# ์ ์ก๊ณ ์ค์บ์ rc=0 ยซ๊นจ๋ยป ์ ๋ธ๋ค. ์๊ฐ๊ฒ์ฌ๋ ์๊ธฐ ์นด๋๋ฆฌ์ ํจํด์ ์ฐ๋ฏ๋ก
|
|
323
|
+
# **์ง์ง ํจํด์ ๊นจ์ง์ ๊ตฌ์กฐ์ ์ผ๋ก ๋ชป ๋ณธ๋ค** โ ๊ทธ๋์ ์ฌ๊ธฐ์ ๋ด์ผ ํ๋ค.
|
|
324
|
+
# ๐ฅ P3 ๋ํ: grep ์ ์ ์ ยซ๋ฏธ๋งค์นยป(rc=1)๊ฐ `set -e` ํธ์ถ์๋ฅผ ์ฃฝ์ธ๋ค.
|
|
325
|
+
if _line_hits=$(printf '%s\n' "$input" | grep -iE "$re" 2>/dev/null); then _lg=0; else _lg=$?; fi
|
|
326
|
+
if [ "$_lg" -gt 1 ]; then
|
|
327
|
+
echo " โ INSTRUMENT DEAD โ grep failed (rc=$_lg) on pattern: $re"
|
|
328
|
+
echo " โ INSTRUMENT DEAD โ grep failed (rc=$_lg) on pattern: $re" >&2
|
|
329
|
+
echo " A broken pattern row scans NOTHING and reads as clean. NOT SCANNED." >&2
|
|
330
|
+
return 3
|
|
331
|
+
fi
|
|
219
332
|
while IFS= read -r line; do
|
|
220
333
|
[ -z "$line" ] && continue
|
|
221
|
-
|
|
334
|
+
_psa_path="${line%%$'\t'*}"; body="${line#*$'\t'}"
|
|
222
335
|
# EVERY match on the line. Taking only the first let a documented placeholder earlier on the
|
|
223
336
|
# line shield a real token later on it.
|
|
337
|
+
if _tok_hits=$(printf '%s' "$body" | grep -oiE "$re" 2>/dev/null); then _tg=0; else _tg=$?; fi
|
|
338
|
+
if [ "$_tg" -gt 1 ]; then
|
|
339
|
+
echo " โ INSTRUMENT DEAD โ grep -o failed (rc=$_tg) on pattern: $re"
|
|
340
|
+
echo " โ INSTRUMENT DEAD โ grep -o failed (rc=$_tg) on pattern: $re" >&2
|
|
341
|
+
return 3
|
|
342
|
+
fi
|
|
224
343
|
while IFS= read -r tok; do
|
|
225
344
|
[ -z "$tok" ] && continue
|
|
226
345
|
printf '%s' "$tok" | grep -qiE "$PSA_PLACEHOLDER" && continue
|
|
227
|
-
if [ "$sev" = "LOW" ] && psa_low_allowlisted "$
|
|
228
|
-
if psa_pair_allowlisted "$
|
|
229
|
-
echo
|
|
346
|
+
if [ "$sev" = "LOW" ] && psa_low_allowlisted "$_psa_path"; then continue; fi
|
|
347
|
+
if psa_pair_allowlisted "$_psa_path" "$tok"; then continue; fi
|
|
348
|
+
# ๐ฅ P5: zsh ์ `echo` ๋ ๋ฐฑ์ฌ๋์๋ฅผ ํด์ํ๋ค โ ๊ฒฝ๋ก `dir\two` ๊ฐ ํญ์ผ๋ก ๋ณํด
|
|
349
|
+
# ์ฆ๊ฑฐ ๋ฌธ์์ด์ด ์์๋๋ค(์ค์ธก). fail-open ์ ์๋๋ ์ฆ๊ฑฐ๋ ์ฆ๊ฑฐ์ฌ์ผ ํ๋ค.
|
|
350
|
+
printf ' โ %s leak โ %s: \x27%s\x27\n' "$sev" "${_psa_path}" "$tok"
|
|
230
351
|
hit=1
|
|
231
352
|
done <<PSA_TOK
|
|
232
|
-
$
|
|
353
|
+
$_tok_hits
|
|
233
354
|
PSA_TOK
|
|
234
355
|
done <<PSA_LINES
|
|
235
|
-
$
|
|
356
|
+
$_line_hits
|
|
236
357
|
PSA_LINES
|
|
237
358
|
done <<PSA_PAT
|
|
238
359
|
$PSA_STREAM
|
|
@@ -285,15 +406,42 @@ _psa_can_assign() {
|
|
|
285
406
|
# the caller's shell before the restore line. Status capture is now inside an `if`.
|
|
286
407
|
psa_require_live() {
|
|
287
408
|
local saved_stream saved_allow saved_stream_set saved_allow_set tmpd out rc _canary
|
|
409
|
+
local _nonce _tag_rc
|
|
288
410
|
saved_stream="${PSA_STREAM-}"; saved_allow="${PSA_ALLOWLIST-}"
|
|
289
411
|
saved_stream_set=""; saved_allow_set=""
|
|
290
412
|
[ "${PSA_STREAM+x}" = "x" ] && saved_stream_set=1
|
|
291
413
|
[ "${PSA_ALLOWLIST+x}" = "x" ] && saved_allow_set=1
|
|
292
414
|
tmpd=$(mktemp -d 2>/dev/null) || { echo " โ INSTRUMENT DEAD โ mktemp unavailable." >&2; return 1; }
|
|
415
|
+
# ๐ฅ P1 (cross-family 3๋ผ์ด๋): ์นด๋๋ฆฌ์๊ฐ **๊ณ ์ ๋ฌธ์์ด**์ด๋ฉด, ์
๋ ฅ์ ์ฝ์ง๋ ์๊ณ
|
|
416
|
+
# ๊ทธ ๋ฌธ์์ด์ rc=0 ์ผ๋ก ๋ฑ๋ ์์กฐ ์์ฐ์๊ฐ ์๊ฐ๊ฒ์ฌ๋ฅผ ํต๊ณผํ๋ค. ์ธ ๋ผ์ด๋๊ฐ ๊ณต์ ํ
|
|
417
|
+
# ์ผ๊ตด โใ์ํ(rc=0)๋ฅผ ์๋ฏธ(์์ฐ์ด ์ ํจํ๋ค)๋ก ํ์ฅใโ ์ ๋ง์ง๋ง ์์ฌ๋ค.
|
|
418
|
+
# ์คํ๋ง๋ค ์ nonce ๋ฅผ ๋ง๋ค๋ฉด **ํ๋์ฝ๋ฉ๋ ์์กฐ๋ ๊ทธ ๊ฐ์ ์ ์ ์๋ค.**
|
|
419
|
+
_nonce=$(head -c 12 /dev/urandom 2>/dev/null | od -An -tx1 2>/dev/null | tr -d ' \n')
|
|
420
|
+
# ๐ฅ **์ ๋ขฐ ๊ฒฝ๊ณ โ ์ฌ๊ธฐ์ ๋ผ์ด๋๋ฅผ ๋ฉ์ถ๋ ์ด์ (2026-08-21, 6๋ผ์ด๋ ํ ํ์ ).**
|
|
421
|
+
# ์ด ์๊ฐ๊ฒ์ฌ๊ฐ ๋ง๋ ๊ฒ: ์์ฐ์๊ฐ **๊นจ์ก๊ฑฐ๋**(awk ๋ถ์ฌยทcrashยท์ถ๋ ฅ ํผ์), **PATH ๋ก
|
|
422
|
+
# ๊ฐ๋ ค์ก๊ฑฐ๋**(์ธ๋ถ ๋ฐ์ด๋๋ฆฌ shadowing), ์
๋ ฅ์ **์ ์ฝ๊ณ ** ๊ณ ์ /์ ๋๊ฐ์ ๋ฑ๋ ๊ฒฝ์ฐ.
|
|
423
|
+
# ์ด ์๊ฐ๊ฒ์ฌ๊ฐ **๋ชป** ๋ง๋ ๊ฒ: ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ **์์ฑํ ์
ธ ์์์ ํจ์๋ฅผ ์ ์ํ ์ ์๋**
|
|
424
|
+
# ์๋. ๊ทธ ์๋๋ `psa_scan_tagged(){ return 0; }` ํ ์ค์ด๋ฉด ๋์ด๋ผ nonceยทcmpยท์ฌ๋งํฌ๋ฅผ
|
|
425
|
+
# ์๋ฌด๋ฆฌ ์กฐ์ฌ๋ ์์ฉ์ด ์๋ค โ **์ด๋ฏธ ์ ๋ขฐ ์์ชฝ**์ด๋ค. 6๋ผ์ด๋ ๋ฐ๋ก ๋์ด ์ ํํ ๊ทธ ๋ถ๋ฅ์๊ณ
|
|
426
|
+
# (`cmp(){ return 0; }` ยท `ln -sf` ๋ก tagged ๋ฐ๊ฟ์น๊ธฐ), ๊ฐ์ผ ๋ ์๋ฆฌ(`command cmp` ยท
|
|
427
|
+
# ์ฌ๋งํฌ ๊ฑฐ๋ถ)๋ ํ์ง๋ง **๊ทธ ์ถ์ ๋ซ์๋ค๊ณ ์ฃผ์ฅํ์ง ์๋๋ค.**
|
|
428
|
+
# ๐ฅ R5-1: **fallback ์ ์์ด๋ค.** 5๋ผ์ด๋ ์ค์ธก โ
|
|
429
|
+
# `${tmpd##*/}` ํ์ โ ์์ฐ์๊ฐ ๊ฒฝ๋ก๋ฅผ ์ธ์๋ก ๋ฐ์ผ๋ฏ๋ก ์ ๋ ๊ฐ๋ฅ (4๋ผ์ด๋์์ ๊ต์ฒด)
|
|
430
|
+
# `${RANDOM}${RANDOM}$$${SECONDS}` โ **์์ฐ์๊ฐ ๊ฐ์ ์
ธ์์ ๊ทธ๋๋ก ์ฌ๊ณ์ฐํด ํต๊ณผ**(zsh ์ค์ธก rc=0),
|
|
431
|
+
# `RANDOM`/`SECONDS` ๊ฐ unset ์ด๋ฉด ์ฌ์ค์ `$$` ํ๋๋ผ `ps` ๋ก๋ ์ฝํ๋ค(bash ์ค์ธก rc=0)
|
|
432
|
+
# โ ใ์์ธก ๋ถ๊ฐํ ๊ฐใ์ **์ปค๋ ์ํธ๋กํผ์์๋ง** ๋์จ๋ค. ๋ชป ์ป์ผ๋ฉด ๊ทธ๊ฑด ์ฝํ nonce ๊ฐ ์๋๋ผ
|
|
433
|
+
# **๋ฏธ์ธก์ **์ด๋ค. ๋ ๋ฒ์งธ ์ถ์ฒ(openssl)๊น์ง ์๋ํ๊ณ , ๊ทธ๊ฒ๋ ์์ผ๋ฉด fail-closed.
|
|
434
|
+
[ -n "$_nonce" ] || _nonce=$(openssl rand -hex 12 2>/dev/null | tr -cd 'A-Za-z0-9')
|
|
435
|
+
if [ -z "$_nonce" ]; then
|
|
436
|
+
echo " โ INSTRUMENT DEAD โ no kernel entropy source (/dev/urandom, openssl); cannot mint an unguessable nonce." >&2
|
|
437
|
+
echo " A guessable nonce is not a weaker probe, it is NO probe โ a producer can replay it." >&2
|
|
438
|
+
rm -rf "$tmpd" 2>/dev/null || :
|
|
439
|
+
return 1
|
|
440
|
+
fi
|
|
293
441
|
# Round 4 refuted the round-3 fix: `|| :` does NOT rescue an assignment to a readonly variable โ
|
|
294
442
|
# bash aborts the shell before the `||` is ever considered. So the only safe move is to REFUSE
|
|
295
443
|
# before mutating anything. Detected, reported as DEAD, and the caller survives.
|
|
296
|
-
_canary=$(printf 'HIGH\
|
|
444
|
+
_canary=$(printf 'HIGH\tPSA_SELFTEST_CANARY_%s' "$_nonce")
|
|
297
445
|
if ! _psa_can_assign PSA_STREAM "$_canary" || ! _psa_can_assign PSA_ALLOWLIST /dev/null; then
|
|
298
446
|
echo " โ INSTRUMENT DEAD โ PSA_STREAM/PSA_ALLOWLIST cannot take the values the self-test needs." >&2
|
|
299
447
|
echo " (readonly, or an attribute such as \`declare -i\` that rejects the value)" >&2
|
|
@@ -303,9 +451,47 @@ psa_require_live() {
|
|
|
303
451
|
fi
|
|
304
452
|
PSA_STREAM="$_canary"
|
|
305
453
|
PSA_ALLOWLIST=/dev/null
|
|
306
|
-
printf '
|
|
307
|
-
#
|
|
308
|
-
|
|
454
|
+
printf 'PSA_SELFTEST_CANARY_%s\n' "$_nonce" > "$tmpd/canary" 2>/dev/null
|
|
455
|
+
# ๐ฅ ์์ฐ์์ ๋งค์ฒ๋ฅผ **๋ถ๋ฆฌํด์** ์ํ๋ฅผ ๊ฐ๊ฐ ๋ณธ๋ค (cross-family ๋ฐ๋ก 4, 2026-08-21).
|
|
456
|
+
# ์ดํ์ `awk โฆ | psa_scan_tagged` ํ ํ์ดํ์ ์ต์ข
rc ๋ง ๋ดค๋๋ฐ, ๊ทธ๋ฌ๋ฉด
|
|
457
|
+
# **ยซ์์ฐ์ ์คํจ์ rc=1ยป** ๊ณผ **ยซ์ค์บ๋๊ฐ ์ ์ถ์ ์ฐพ์์ rc=1ยป** ์ด ๊ฐ์ ๊ฐ์ผ๋ก ํฉ์ณ์ง๋ค.
|
|
458
|
+
# ์ค์ธก ์์กฐ: ์คํจํ๋ `awk` ๊ฐ ์นด๋๋ฆฌ์ ํ์ ์ธ์ํ๊ณ `return 1` ํ๊ฒ ๋ง๋ค๋ฉด
|
|
459
|
+
# ์ ์
ธ์์ `psa_require_live` ๊ฐ **rc=0(์ด์์์)** ์ ๋๋ค. ์ฆ ์๊ฐ๊ฒ์ฌ ํ์ ์์ฒด๊ฐ
|
|
460
|
+
# ์์กฐ ๊ฐ๋ฅํ๋ค. ํ๊น
๊ฒฐ๊ณผ๋ฅผ ๋จผ์ **๋ฌผ์งํ**ํ๊ณ ๊ทธ rc ๋ฅผ ๋ฐ๋ก ํ์ธํ๋ค.
|
|
461
|
+
# ๐ฅ P3: `x=$(cmd); rc=$?` ๋ `set -e` ์๋์ **์
ธ์ ์ฆ์ ์ฃฝ์ธ๋ค** โ ์๋ ๋ณต์๋ฌธ๊ณผ
|
|
462
|
+
# `rm -rf` ์ ๋๋ฌํ์ง ๋ชปํ๊ณ PSA_STREAM ์ด **์นด๋๋ฆฌ์์ธ ์ฑ๋ก ๋จ๋๋ค**(์ค์ธก, bash).
|
|
463
|
+
# ์กฐ๊ฑด๋ฌธ ์์ ๋์
์ errexit ๋ฉด์ ๋ค.
|
|
464
|
+
# ๐ฅ R4-3: `$(...)` ๋ **ํํ ๊ฐํ์ ์ง์ด๋ค** โ ์ ๋ต ์ค ๋ค์ ๋น ์ค์ ์๋ฌด๋ฆฌ ๋ถ์ฌ๋
|
|
465
|
+
# ๋ฌธ์์ด ๋น๊ต๊ฐ ํต๊ณผํ๋ค(์ค์ธก rc=0). ใ์ ํํ ๋ฌธ์์ด ๋์กฐใ๋ผ๊ณ ์ ์ด๋๊ณ ์ ํํ์ง ์์๋ค.
|
|
466
|
+
# ์์ฐ์ ์ถ๋ ฅ์ **ํ์ผ๋ก ๋ฌผ์งํ**ํด์ `cmp` ๋ก ๋ฐ์ดํธ ๋น๊ตํ๋ค. stderr ๋ ๋ฐ๋ก ๋ฐ๋๋ค.
|
|
467
|
+
if awk -v P="psa/selftest" '{print P "\t" $0}' "$tmpd/canary" > "$tmpd/tagged" 2>"$tmpd/err"; then _tag_rc=0; else _tag_rc=$?; fi
|
|
468
|
+
# ๐ฅ P1: ์ํ(rc)๋ง์ด ์๋๋ผ **๋ด์ฉ**์ ๋์กฐํ๋ค. ์์ฐ์๊ฐ ยซ์
๋ ฅ์ ์ฝ์ด์ ํ๊น
ํ๋๊ฐยป ๋
|
|
469
|
+
# rc ๋ก ์ ์ ์๊ณ , nonce ๋ฅผ ํฌํจํ ์ ํํ ๋ฌธ์์ด ์ผ์น๋ก๋ง ์ ์ ์๋ค.
|
|
470
|
+
# ๐ฅ R5-2: ์ดํ์ ๊ธฐ๋๊ฐ๋ `$tmpd/expect` ํ์ผ์ ๋๋๋ฐ, **์์ฐ์๋ `$tmpd/canary` ๋ฅผ ์ธ์๋ก
|
|
471
|
+
# ๋ฐ์ผ๋ฏ๋ก `$tmpd` ๋ฅผ ์๊ณ expect ๋ฅผ ๋ฎ์ด์ธ ์ ์๋ค** โ ํ๋ฆฐ ๋ฐ์ดํธ๋ฅผ ์์ชฝ์ ์จ๋ฃ์ผ๋ฉด
|
|
472
|
+
# `cmp` ๊ฐ ํต๊ณผํ๋ค(์ ์
ธ ์ค์ธก rc=0). ๊ธฐ๋๊ฐ์ **ํ๋ก์ธ์ค ์์๋ง** ๋๊ณ ํ์ดํ๋ก ๋จน์ธ๋ค.
|
|
473
|
+
# (`cmp file -` ๋ stdin ์ ๋ ๋ฒ์งธ ํผ์ฐ์ฐ์๋ก ์ฝ๋๋ค. cmp ๋ถ์ฌ๋ rcโ 0 โ fail-closed, ์ค์ธก.)
|
|
474
|
+
# ๐ฅ R6-1: `cmp` ๋ฅผ ์
ธ์ด ํด์ํ๋ฉด **ํจ์๋ก ๊ฐ๋ ค์ง๋ค** โ `cmp(){ return 0; }` ํ๋๋ก ๋์กฐ๊ฐ
|
|
475
|
+
# ํต์งธ๋ก ๋ฌด๋ ฅํ๋๋ค(์ ์
ธ ์ค์ธก rc=0). `command` ๋ ํจ์๋ฅผ ๊ฑด๋๋ด๋ค.
|
|
476
|
+
# ๐ฅ R6-2: ์์ฐ์๋ `$tmpd` ๋ฅผ ์๋ฏ๋ก stdout ์๋ ํ๋ฆฐ ๋ฐ์ดํธ๋ฅผ ์ฐ๊ณ `tagged` ๋ฅผ **์ ๋ต ํ์ผ๋ก
|
|
477
|
+
# ๊ฐ๋ ์ฌ๋งํฌ๋ก ๋ฐ๊ฟ์น๊ธฐ**ํ ์ ์๋ค(์ ์
ธ ์ค์ธก rc=0). ๋น๊ต ๋์์ด ยซawk ๊ฐ ์ค์ ๋ก ์ด ํ์ผยป
|
|
478
|
+
# ์ธ์ง ๋จผ์ ํ์ธํ๋ค โ ์ฌ๋งํฌ๊ฐ ์๋ ๋ณดํต ํ์ผ์ด์ด์ผ ํ๋ค.
|
|
479
|
+
if [ -L "$tmpd/tagged" ] || [ ! -f "$tmpd/tagged" ]; then
|
|
480
|
+
rm -rf "$tmpd" 2>/dev/null || :
|
|
481
|
+
if [ -n "${saved_stream_set:-}" ]; then PSA_STREAM="$saved_stream"; else unset PSA_STREAM; fi
|
|
482
|
+
if [ -n "${saved_allow_set:-}" ]; then PSA_ALLOWLIST="$saved_allow"; else unset PSA_ALLOWLIST; fi
|
|
483
|
+
echo " โ INSTRUMENT DEAD โ the self-test output was replaced (symlink or non-regular file)." >&2
|
|
484
|
+
return 1
|
|
485
|
+
fi
|
|
486
|
+
if [ "$_tag_rc" -ne 0 ] || ! printf 'psa/selftest\tPSA_SELFTEST_CANARY_%s\n' "$_nonce" | command cmp -s "$tmpd/tagged" -; then
|
|
487
|
+
rm -rf "$tmpd" 2>/dev/null || :
|
|
488
|
+
if [ -n "${saved_stream_set:-}" ]; then PSA_STREAM="$saved_stream"; else unset PSA_STREAM; fi
|
|
489
|
+
if [ -n "${saved_allow_set:-}" ]; then PSA_ALLOWLIST="$saved_allow"; else unset PSA_ALLOWLIST; fi
|
|
490
|
+
echo " โ INSTRUMENT DEAD โ the self-test PRODUCER did not tag this run's nonce (rc=$_tag_rc)." >&2
|
|
491
|
+
echo " A canary printed by a failing โ or input-ignoring โ producer is not evidence the matcher ran." >&2
|
|
492
|
+
return 1
|
|
493
|
+
fi
|
|
494
|
+
if out=$(psa_scan_tagged < "$tmpd/tagged" 2>&1); then
|
|
309
495
|
rc=0
|
|
310
496
|
else
|
|
311
497
|
rc=$?
|
|
@@ -317,7 +503,7 @@ psa_require_live() {
|
|
|
317
503
|
if [ -n "${saved_stream_set:-}" ]; then PSA_STREAM="$saved_stream"; else unset PSA_STREAM; fi
|
|
318
504
|
if [ -n "${saved_allow_set:-}" ]; then PSA_ALLOWLIST="$saved_allow"; else unset PSA_ALLOWLIST; fi
|
|
319
505
|
case "$out" in
|
|
320
|
-
*
|
|
506
|
+
*"PSA_SELFTEST_CANARY_$_nonce"*) [ "$rc" -eq 1 ] && return 0 ;;
|
|
321
507
|
esac
|
|
322
508
|
echo " โ INSTRUMENT DEAD โ the scanner did not report a synthetic known-positive." >&2
|
|
323
509
|
echo " self-test rc=$rc out=[$out]" >&2
|