1
00:00:00,000 --> 00:00:02,944
Welcome to the show - I'm Lachlan Reed
here with James Turner,

2
00:00:02,944 --> 00:00:07,504
and James, I love updates that look like
a flea and kick like a mule.

3
00:00:07,504 --> 00:00:11,892
Claude Code 2.

4
00:00:07,504 --> 00:00:11,892
1.113 sounds like a sleepy patch release,

5
00:00:11,892 --> 00:00:16,988
but it swaps the CLI from spawning
bundled JavaScript to launching a

6
00:00:16,988 --> 00:00:19,312
native Claude Code binary.

7
00:00:19,312 --> 00:00:23,860
And not one giant blob, either - it ships
as a per-platform optional

8
00:00:23,860 --> 00:00:30,345
npm dependency.That phrase -
"per-platform optional npm dependency"

9
00:00:30,345 --> 00:00:32,221
- is the whole story.

10
00:00:32,221 --> 00:00:36,845
Instead of one universal JavaScript
package hauling everything around,

11
00:00:36,845 --> 00:00:40,639
npm can pull the right binary for macOS,

12
00:00:40,639 --> 00:00:42,673
Linux, whatever box you're on.

13
00:00:42,673 --> 00:00:45,783
That means the JS bundle gets smaller,

14
00:00:45,783 --> 00:00:50,068
and the startup path gets shorter because
you're not booting a JS runtime

15
00:00:50,068 --> 00:00:54,478
just to get to the tool.

16
00:00:50,068 --> 00:00:54,478
And startup pain is REAL.

17
00:00:54,478 --> 00:00:58,073
In local dev, a slow tool is annoying.

18
00:00:58,073 --> 00:01:01,662
In CI,
where you might cold-start over and over,

19
00:01:01,662 --> 00:01:04,295
it's like stepping in the same pothole
all day.

20
00:01:04,295 --> 00:01:09,085
First run especially - if a command takes
that extra beat,

21
00:01:09,085 --> 00:01:10,600
you feel it in your bones.

22
00:01:10,600 --> 00:01:13,940
I've had builds where the script was
technically fine,

23
00:01:13,940 --> 00:01:17,054
but the toolchain woke up like an old
trail bike in winter.

24
00:01:17,054 --> 00:01:22,063
"Cold-start
over and over" is exactly it.

25
00:01:22,063 --> 00:01:27,052
The concrete wins here are smaller JS
bundle size and faster cold starts.

26
00:01:27,052 --> 00:01:31,390
And those aren't abstract engineering
trophies - listeners will feel

27
00:01:31,390 --> 00:01:36,038
them on first-run and in CI-heavy
workflows where every second of startup

28
00:01:36,038 --> 00:01:40,425
latency gets multiplied.

29
00:01:36,038 --> 00:01:40,425
What's funny is a native binary can sound
like

30
00:01:40,425 --> 00:01:41,832
MORE moving parts.

31
00:01:41,832 --> 00:01:44,147
You hear "binary" and think, oh beauty,

32
00:01:44,147 --> 00:01:46,462
now we've got platform weirdness,

33
00:01:46,462 --> 00:01:49,420
maybe signing issues,
maybe some gremlin in the shed.

34
00:01:49,420 --> 00:01:55,220
But here it actually reduces the runtime
mess the CLI has to drag

35
00:01:49,420 --> 00:01:55,220
around.

36
00:01:55,220 --> 00:01:57,156
I think that tension is worth sitting on.

37
00:01:57,156 --> 00:01:59,871
Convenience versus control, right?

38
00:01:59,871 --> 00:02:04,643
JavaScript everywhere feels convenient
because it's one delivery shape.

39
00:02:04,643 --> 00:02:08,879
But a native binary per platform can be
more controlled operationally.

40
00:02:08,879 --> 00:02:13,264
You can update those binaries
independently instead of republishing one

41
00:02:13,264 --> 00:02:18,200
giant JS bundle that has to account for
every environment at once.

42
00:02:13,264 --> 00:02:18,200
So the

43
00:02:18,200 --> 00:02:20,872
"tiny" release is really an architectural
swap.

44
00:02:20,872 --> 00:02:23,708
Not flashy,
but it changes the feeling of the tool.

45
00:02:23,708 --> 00:02:27,418
Less ballast, quicker launch,
fewer runtime gymnastics.

46
00:02:27,418 --> 00:02:31,536
That's the sort of patch note people skip
- then suddenly the CLI feels

47
00:02:31,536 --> 00:02:36,355
snappier and they go, huh, that's nicer.

48
00:02:31,536 --> 00:02:36,355
And trust matters too.

49
00:02:36,355 --> 00:02:39,947
A CLI that starts cleanly and predictably
feels sturdier.

50
00:02:39,947 --> 00:02:43,128
Performance isn't just speed; it's
confidence.

51
00:02:43,128 --> 00:02:46,321
If your tool spends less time doing
invisible setup,

52
00:02:46,321 --> 00:02:49,433
there's less mystery between you and the
command you actually meant to

53
00:02:49,433 --> 00:02:53,463
run.Now the security fix
that made me wince: deny rules

54
00:02:53,463 --> 00:02:57,538
for Bash commands could be bypassed by
wrapping the command in exec wrappers

55
00:02:57,538 --> 00:03:01,090
like env, sudo, watch, ionice, setsid,

56
00:03:01,090 --> 00:03:02,290
and similar helpers.

57
00:03:02,290 --> 00:03:07,127
The painfully simple version was just
prepending one word: env.[questioning

58
00:03:07,127 --> 00:03:09,312
tone] And "one word" is the bit that
sticks.

59
00:03:09,312 --> 00:03:12,750
Not some galaxy-brain exploit - just env.

60
00:03:12,750 --> 00:03:16,023
So if a deny rule was looking at the
first token,

61
00:03:16,023 --> 00:03:21,038
it saw env instead of the thing actually
being executed?

62
00:03:16,023 --> 00:03:21,038
Exactly.

63
00:03:21,038 --> 00:03:25,447
And that's the key lesson: deny rules are
only meaningful if they match

64
00:03:25,447 --> 00:03:27,983
the command as actually executed,

65
00:03:27,983 --> 00:03:30,530
not merely the first token the user typed.

66
00:03:30,530 --> 00:03:34,742
If your parser treats wrappers as
harmless decoration,

67
00:03:34,742 --> 00:03:38,192
your security boundary is basically made
of cardboard.I'm

68
00:03:38,192 --> 00:03:40,318
with you on the bug,
but I wanna push a bit.

69
00:03:40,318 --> 00:03:43,512
Was this a serious bypass in practice,

70
00:03:43,512 --> 00:03:45,508
or one of those "technically true,

71
00:03:45,508 --> 00:03:47,740
edge-case weirdness" situations?

72
00:03:47,740 --> 00:03:53,045
Because sometimes users assume these
rules are magic force fields when

73
00:03:53,045 --> 00:03:56,200
they were really more like guardrails.

74
00:03:53,045 --> 00:03:56,200
No,

75
00:03:56,200 --> 00:04:00,616
I'd call this serious - especially in CI
or any broad-permission setup.

76
00:04:00,616 --> 00:04:05,006
If a team believed a deny rule blocked a
class of Bash commands,

77
00:04:05,006 --> 00:04:09,234
and that block could be sidestepped with
env or sudo or watch,

78
00:04:09,234 --> 00:04:12,941
then the protection was weaker than the
policy implied.

79
00:04:12,941 --> 00:04:17,826
In a locked-down environment, "we thought
this was denied" is not a small

80
00:04:17,826 --> 00:04:20,837
misunderstanding.

81
00:04:17,826 --> 00:04:20,837
CI is the bit that changes it for me.

82
00:04:20,837 --> 00:04:24,826
Because in a local shell,
maybe you catch weird behavior.

83
00:04:24,826 --> 00:04:27,454
In automation, stuff just runs.

84
00:04:27,454 --> 00:04:31,365
And if the rule says no, people assume NO,

85
00:04:31,365 --> 00:04:37,295
not "no unless you dress it up in a
different jacket."Right -

86
00:04:37,295 --> 00:04:40,067
"dress it up in a different jacket" is
perfect.

87
00:04:40,067 --> 00:04:43,166
Wrappers don't change the underlying
intent.

88
00:04:43,166 --> 00:04:45,562
They just alter how the command is
invoked.

89
00:04:45,562 --> 00:04:50,560
Security tooling has to see through that
layer.

90
00:04:45,562 --> 00:04:50,560
Also,

91
00:04:50,560 --> 00:04:53,194
let's be honest, env looks harmless.

92
00:04:53,194 --> 00:04:54,785
It's practically beige.

93
00:04:54,785 --> 00:04:57,817
You see env and think environment
variables,

94
00:04:57,817 --> 00:04:58,452
no drama.

95
00:04:58,452 --> 00:05:02,126
But if env becomes a tunnel under the
deny rule,

96
00:05:02,126 --> 00:05:05,438
that's a nasty little gap.

97
00:05:02,126 --> 00:05:05,438
And now that gap is closed.

98
00:05:05,438 --> 00:05:09,892
Which is good, because the simplest
bypasses are usually the ones that

99
00:05:09,892 --> 00:05:11,112
get used.

100
00:05:11,112 --> 00:05:16,625
Nobody forgets a one-word workaround.

101
00:05:11,112 --> 00:05:16,625
I had a very specific reaction to

102
00:05:16,625 --> 00:05:19,390
this next fix,
because my first instinct was,

103
00:05:19,390 --> 00:05:23,270
"find is fine." Read-only vibe.

104
00:05:23,270 --> 00:05:25,702
Harmless little bloodhound sniffing
through files.

105
00:05:25,702 --> 00:05:31,093
But the `Bash(find:*)` allow-rule fix
closes a loophole where `find -exec`

106
00:05:31,093 --> 00:05:35,344
and `find -delete` were being
auto-approved just because the base

107
00:05:31,093 --> 00:05:35,344
command

108
00:05:35,344 --> 00:05:40,268
was `find`.And those two flags
are the whole problem.

109
00:05:40,268 --> 00:05:44,107
`find -exec` can run arbitrary commands.

110
00:05:44,107 --> 00:05:47,787
`find -delete` can remove files.

111
00:05:47,787 --> 00:05:52,273
So approving plain `find` should NEVER
have implied permission for either

112
00:05:52,273 --> 00:05:56,497
of those behaviors.`-exec` is the one
that changes the personality of

113
00:05:56,497 --> 00:05:57,277
the tool.

114
00:05:57,277 --> 00:06:00,870
That's not searching anymore - that's
outsourcing action.

115
00:06:00,870 --> 00:06:06,048
"Find these files, then do whatever I
say." That's a different beast.[responds

116
00:06:06,048 --> 00:06:06,758
quickly] Yes.

117
00:06:06,758 --> 00:06:11,190
This is where security bugs hide: in the
gap between "looks harmless"

118
00:06:11,190 --> 00:06:16,408
and "has side effects." A rule that keys
off the command name alone misses

119
00:06:16,408 --> 00:06:19,173
the semantic jump from inspection to
execution.

120
00:06:19,173 --> 00:06:23,856
Same binary,
totally different risk profile.

121
00:06:19,173 --> 00:06:23,856
Let me try to explain it

122
00:06:23,856 --> 00:06:24,192
back.

123
00:06:24,192 --> 00:06:27,384
If I allow `find`, I probably mean,

124
00:06:27,384 --> 00:06:30,421
"you can look around." I do NOT mean,

125
00:06:30,421 --> 00:06:34,539
"and while you're at it, run arbitrary
commands with `-exec` or start

126
00:06:34,539 --> 00:06:39,145
deleting things with `-delete`." Almost
like allowing a torch and accidentally

127
00:06:39,145 --> 00:06:41,023
allowing a flamethrower because,

128
00:06:41,023 --> 00:06:45,391
eh,
both make fire.Slightly dramatic,

129
00:06:45,391 --> 00:06:46,191
but yes.

130
00:06:46,191 --> 00:06:49,529
The permission should map to behavior,

131
00:06:49,529 --> 00:06:50,567
not just branding.

132
00:06:50,567 --> 00:06:53,679
`find` is not one capability.

133
00:06:53,679 --> 00:06:59,200
It's a command with modes, and some modes
cross directly into side effects.

134
00:06:53,679 --> 00:06:59,200
This

135
00:06:59,200 --> 00:07:03,376
one feels especially real because loads
of devs trust `find` on instinct.

136
00:07:03,376 --> 00:07:04,015
I do.

137
00:07:04,015 --> 00:07:06,400
You type it when you're poking around a
repo,

138
00:07:06,400 --> 00:07:09,910
looking for config files,
cleaning up some mystery directory.

139
00:07:09,910 --> 00:07:15,154
Then `-delete` wanders in and suddenly
your "just having a look" command

140
00:07:15,154 --> 00:07:20,894
is carrying a chainsaw.

141
00:07:15,154 --> 00:07:20,894
And the fix matters because it restores
that intuition

142
00:07:20,894 --> 00:07:21,950
properly.

143
00:07:21,950 --> 00:07:27,145
Plain `find` can be treated differently
from `find` with dangerous flags.

144
00:07:27,145 --> 00:07:31,779
That's better security,
but also better mental models for users.

145
00:07:31,779 --> 00:07:36,618
You shouldn't have to remember that a
read-looking command secretly inherited

146
00:07:36,618 --> 00:07:39,714
write-like approval.Last one,

147
00:07:39,714 --> 00:07:41,790
and it's a classic filesystem gotcha.

148
00:07:41,790 --> 00:07:48,260
On macOS, `/etc` is really a symlink to
`/private/etc`.

149
00:07:48,260 --> 00:07:51,773
Same story for `/var`, `/tmp`,

150
00:07:51,773 --> 00:07:53,053
and `/home`.

151
00:07:53,053 --> 00:07:58,716
In 2.

152
00:07:53,053 --> 00:07:58,716
1.113, `/private/etc`, `/private/var`,

153
00:07:58,716 --> 00:08:04,673
`/private/tmp`, and `/private/home` are
now treated as dangerous removal

154
00:08:04,673 --> 00:08:12,309
targets under `Bash(rm:*)` rules.

155
00:08:04,673 --> 00:08:12,309
The token there is `/private/etc`.

156
00:08:12,309 --> 00:08:17,631
Because if your rule protected the pretty
path - `/etc` - but not the

157
00:08:17,631 --> 00:08:22,107
real target under it, then someone could
bypass the protection just by

158
00:08:22,107 --> 00:08:25,965
aiming at `/private/etc` instead.

159
00:08:25,965 --> 00:08:30,877
Same destination,
different sign on the road.

160
00:08:25,965 --> 00:08:30,877
Exactly.

161
00:08:30,877 --> 00:08:32,873
And this wasn't theoretical.

162
00:08:32,873 --> 00:08:36,535
The filesystem path resolution created a
gap between the human-readable

163
00:08:36,535 --> 00:08:39,811
rule and the actual destructive target.

164
00:08:39,811 --> 00:08:41,719
Humans think in the friendly path.

165
00:08:41,719 --> 00:08:43,634
The system follows the symlink.

166
00:08:43,634 --> 00:08:46,904
If your rule engine only respected the
friendly version,

167
00:08:46,904 --> 00:08:51,130
`rm` protections had a hole in
them.That's such a macOS

168
00:08:51,130 --> 00:08:52,058
little trick, isn't it?

169
00:08:52,058 --> 00:08:56,016
Like the house has a nice front door and
then a secret service entrance

170
00:08:56,016 --> 00:08:56,766
around the back.

171
00:08:56,766 --> 00:09:02,909
And the important detail is this fix is
for `rm` allow rules specifically.

172
00:09:02,909 --> 00:09:08,080
Read operations are unaffected.

173
00:09:02,909 --> 00:09:08,080
Right - that distinction matters.

174
00:09:08,080 --> 00:09:12,174
It tightens destructive actions without
making ordinary inspection workflows

175
00:09:12,174 --> 00:09:12,942
more painful.

176
00:09:12,942 --> 00:09:16,740
So you're not broadly punishing reads;
you're being stricter where the

177
00:09:16,740 --> 00:09:19,520
blast radius is file removal.

178
00:09:16,740 --> 00:09:19,520
Which,

179
00:09:19,520 --> 00:09:21,473
honestly,
is how you want these fixes done.

180
00:09:21,473 --> 00:09:23,589
Don't make everyone's day worse.

181
00:09:23,589 --> 00:09:26,341
Just stop the foot-gun from firing
through the floorboards.

182
00:09:26,341 --> 00:09:29,292
If I'm reading a file in `/private/etc`,

183
00:09:29,292 --> 00:09:29,852
fine.

184
00:09:29,852 --> 00:09:33,548
If I'm removing stuff there under a rule
that was supposed to protect

185
00:09:33,548 --> 00:09:35,249
`/etc`...

186
00:09:35,249 --> 00:09:39,286
yeah, nah.And this leaves teams
with a slightly uncomfortable

187
00:09:39,286 --> 00:09:39,790
question.

188
00:09:39,790 --> 00:09:43,304
If your current rules only protect the
pretty path,

189
00:09:43,304 --> 00:09:46,927
not the real one underneath,
how many other "safe" assumptions in your

190
00:09:46,927 --> 00:09:51,439
automation are only symlink-deep?

191
00:09:46,927 --> 00:09:51,439
That's the bit to sit with.

192
00:09:51,439 --> 00:09:54,958
Tiny version number, big trust audit.

193
00:09:54,958 --> 00:09:57,639
Catch you next time.

194
00:09:54,958 --> 00:09:57,639
See you then.
