~13 min · guardrails, hooks, implementation, design
Level 0젖은 흙
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
남은 자리는 하나뿐
금지된 행위가 흔적을 안 남기면 검사를 그 뒤에 돌릴 수 없어. 도중에도 못 돌려. 도중이 없거든. 셸 명령은 벌어지거나 안 벌어지거나 둘 중 하나야. 그러면 자리는 딱 하나 남아. 명령을 돌리기로 한 결정이랑 명령이 도는 것 사이. 실행 전 훅은 여기서 영리한 선택이 아니라 남은 유일한 좌석이야.
그 말은 가드가 앉은 자리에서 딱딱한 제약을 물려받는다는 뜻이야. 모든 명령에 대해 도니까. 규칙이랑 아무 상관 없는 수백 개까지 전부. 그래서 빨라야 하고, 네트워크 호출을 절대 하면 안 되고, 자기 버그로 닫히면 안 돼. 시스템의 모든 동작에 지연이나 불안정을 얹는 가드는 일주일 안에 꺼지고, 규칙은 도로 산문이 되니까.
가드가 실제로 아는 것
세션이 작업을 집을 때 써둔 작은 로컬 표식 파일을 읽어. 어떤 대상인지, 어떤 파이프라인인지, 어떤 단계가 입력을 제한하는지, 지금까지 어떤 단계가 기록됐는지. "지금 봉인이 서 있나"에 답하기엔 그걸로 충분하고, 선을 타고 아무것도 안 물어도 돼. 그 표식은 엔진이 이미 가진 사실의 사본이고, 선점이 끝나면 지워져. 착지로든, 포기로든, 명시적 해제로든.
마지막 대목이 살림살이처럼 들리는데 아냐. 작업이 끝난 뒤에도 계속 걸리는 가드는 꺼지는 종류야. 초기 실행 하나가 선점은 해제됐는데 표식은 살아 있는 채로 끝났고, 그 세션은 한참 동안 자기가 완전히 할 자격이 있는 명령에 대해 거부랑 싸웠어. 과잉 봉인은 안전한 방향으로 실패하지만, 끝나기도 할 때만 그래.
다들 놀라는 규칙: 세그먼트마다 판정한다
가드는 명령을 파이프 세그먼트로 쪼개서 각각 따로 판정해. 여기서 이 물건에 대한 직관이 전부 깨져. 앞쪽에서 한 디렉터리 이동도 계산에 들어가. 페이저로 넘기는 파이프는 아무것도 안 좁혀. 의도를 한정하는 주석은 안 보여. 주석은 인자가 아니니까.
처음엔 부당하게 느껴지는 것도 거부해. 어떤 실행이 원어 파일을 하나도 안 고쳤다는 정직한 메모를 쓰려다 막혔어. 메모 안에 금지된 패턴이 들어 있었거든. 맞는 답은 파일 이름 대신 말로 문장을 쓰는 거였고, 거기 드는 이 초가 의도 모형이 없는 가드의 값이야.
가드는 스스로 티가 나는 방향으로 실패하게 설계해. 너무 많이 거부하면 시끄럽고 짜증나고 한 시간 안에 고쳐져. 너무 적게 거부하면 조용하고 몇 주 뒤 감사에서 발견돼. 가드는 정확하게 못 만들어. 그러니 어느 쪽 오류를 갖고 싶은지 고르고, 차단 메시지가 정당하게 진행하는 방법을 정확히 말하게 만들어.
Code
가드, 판정만 남기고 줄인 것·python
import shlex
FORBIDDEN = ".source.json" # the pattern that must not print
PRINTERS = {"cat", "head", "tail", "less", "grep", "rg", "ag",
"jq", "python", "python3", "awk", "sed"}
# NOT here, and this is the honest gap: `ls`, `find`, `tree`.
# The previous lesson called a bare directory listing a breach,
# and this guard does not close it. Naming the gap in the code
# is worth more than a set that claims coverage nobody has.
def breaches(command: str, target_dir: str) -> bool:
"""True if any PIPE SEGMENT could print forbidden content.
Each segment is judged alone. A `cd` in segment 1 does not make
segment 2 safe, and `| head` narrows nothing - it is a second
command that inherits the first one's output.
"""
cwd = "" # a `cd` in an earlier
for segment in split_pipeline(command): # segment still counts
try:
argv = shlex.split(segment, comments=True) # comments are
except ValueError: # NOT scope
return True # unparseable
if not argv: # -> refuse
continue
tool = argv[0].rsplit("/", 1)[-1]
if tool == "cd" and len(argv) > 1:
cwd = argv[1] # carried into later segments
continue
if tool not in PRINTERS:
continue
args = argv[1:]
if any(FORBIDDEN in a for a in args):
return True
# An unscoped sweep at the target directory is the real shape
# every incident took. A narrowing glob is the only exemption.
touches_target = (target_dir in cwd
or any(target_dir in a for a in args))
narrowed = any(a.startswith("--glob") or a.endswith(".target.json")
for a in args)
if touches_target and not narrowed:
return True
return False
# The block message matters as much as the block. It must name the
# legitimate way through, or the next person disables the hook:
#
# "blocked: unscoped read at the sealed directory.
# Add --glob '*.target.json', or read the engine log instead."
네 환경의 흔적 없는 규칙 하나에 실행 전 가드를 써봐. 로직을 넣기 전에 시험 사례 셋부터 주고. 반드시 막혀야 하는 명령 하나, 반드시 통과해야 하는 명령 하나, 그리고 진짜로 헷갈리는 경계 사례 하나. 그다음 차단 메시지를 먼저 쓰고 탐지를 나중에 써. 차단 메시지가 정당한 대안을 못 대면 그 가드는 내보낼 준비가 안 된 거야.
Hint
경계 사례가 설계 문서야. 어느 쪽으로 결론 내든 그 이유를 시험 옆에 적어둬. 여섯 달 뒤에 왜 그 예외가 있는지 모르는 사람이 가드를 조일 거고, 그 메모가 그 사람이 예외를 지우는 걸 막아줘.
Progress
Progress is local-only — sign in to sync across devices.