fix: enforce identity CID size limits (#10949)
* fix: enforce identity CID size limits - validate --inline-limit against verifcid.MaxDigestSize - add error when --hash=identity exceeds size limit - add tests for identity CID overflow scenarios - update help text to show maximum inline limit This prevents creation of unbounded identity CIDs by enforcing the 128-byte limit defined in https://github.com/ipfs/boxo/pull/1018 Fixes #6011 IPIP: https://github.com/ipfs/specs/pull/512
Marcin Rataj committed
Sep 9, 2025 at 20:22 UTC
20f8151628278cb1e1734a208df6aad0104fea03
11 files changed
+345
-15
core/commands/add.go
+8
-1
@@ -16,6 +16,7 @@ import (
16
"github.com/ipfs/boxo/files"
17
mfs "github.com/ipfs/boxo/mfs"
18
"github.com/ipfs/boxo/path"
19
+ "github.com/ipfs/boxo/verifcid"
20
cmds "github.com/ipfs/go-ipfs-cmds"
21
ipld "github.com/ipfs/go-ipld-format"
22
coreiface "github.com/ipfs/kubo/core/coreiface"
@@ -203,7 +204,7 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import
204
cmds.IntOption(maxHAMTFanoutOptionName, "Limit the maximum number of links of a UnixFS HAMT directory node to this (power of 2, multiple of 8). WARNING: experimental, Import.UnixFSHAMTDirectorySizeThreshold is safer. Default: Import.UnixFSHAMTDirectoryMaxFanout"),
205
// Experimental Features
206
cmds.BoolOption(inlineOptionName, "Inline small blocks into CIDs. WARNING: experimental"),
206
- cmds.IntOption(inlineLimitOptionName, "Maximum block size to inline. WARNING: experimental").WithDefault(32),
207
+ cmds.IntOption(inlineLimitOptionName, fmt.Sprintf("Maximum block size to inline. Maximum: %d bytes. WARNING: experimental", verifcid.DefaultMaxIdentityDigestSize)).WithDefault(32),
208
cmds.BoolOption(noCopyOptionName, "Add the file using filestore. Implies raw-leaves. WARNING: experimental"),
209
cmds.BoolOption(fstoreCacheOptionName, "Check the filestore for pre-existing blocks. WARNING: experimental"),
210
cmds.BoolOption(preserveModeOptionName, "Apply existing POSIX permissions to created UnixFS entries. WARNING: experimental, forces dag-pb for root block, disables raw-leaves"),
@@ -262,6 +263,12 @@ https://github.com/ipfs/kubo/blob/master/docs/config.md#import
263
hashFunStr, _ := req.Options[hashOptionName].(string)
264
inline, _ := req.Options[inlineOptionName].(bool)
265
inlineLimit, _ := req.Options[inlineLimitOptionName].(int)
266
+
267
+ // Validate inline-limit doesn't exceed the maximum identity digest size
268
+ if inline && inlineLimit > verifcid.DefaultMaxIdentityDigestSize {
269
+ return fmt.Errorf("inline-limit %d exceeds maximum allowed size of %d bytes", inlineLimit, verifcid.DefaultMaxIdentityDigestSize)
270
+ }
271
+
272
toFilesStr, toFilesSet := req.Options[toFilesOptionName].(string)
273
preserveMode, _ := req.Options[preserveModeOptionName].(bool)
274
preserveMtime, _ := req.Options[preserveMtimeOptionName].(bool)
docs/changelogs/v0.38.md
+13
@@ -28,6 +28,19 @@ Gateway error pages now provide more actionable information during content retri
28
> - **Enhanced error details**: Timeout errors now display the retrieval phase where failure occurred (e.g., "connecting to providers", "fetching data") and up to 3 peer IDs that were attempted but couldn't deliver the content, making it easier to diagnose network or provider issues.
29
> - **Retry button on all error pages**: Every gateway error page now includes a retry button for quick page refresh without manual URL re-entry.
30
31
+#### 🛠️ Identity CID size enforcement and `ipfs files write` fixes
32
+
33
+**Identity CID size limits are now enforced**
34
+
35
+Identity CIDs use [multihash `0x00`](https://github.com/multiformats/multicodec/blob/master/table.csv#L2) to embed data directly in the CID without hashing. This experimental optimization was designed for tiny data where a CID reference would be larger than the data itself, but without size limits it was easy to misuse and could turn into an anti-pattern that wastes resources and enables abuse. This release enforces a maximum of 128 bytes for identity CIDs - attempting to exceed this limit will return a clear error message.
36
+
37
+- `ipfs add --inline-limit` and `--hash=identity` now enforce the 128-byte maximum (error when exceeded)
38
+- `ipfs files write` prevents creation of oversized identity CIDs
39
+
40
+**Multiple `ipfs files write` bugs have been fixed**
41
+
42
+This release resolves several long-standing MFS issues: raw nodes now preserve their codec instead of being forced to dag-pb, append operations on raw nodes work correctly by converting to UnixFS when needed, and identity CIDs properly inherit the full CID prefix from parent directories.
43
+
44
### 📦️ Important dependency updates
45
46
### 📝 Changelog
docs/examples/kubo-as-a-library/go.mod
+1
-1
@@ -7,7 +7,7 @@ go 1.25
7
replace github.com/ipfs/kubo => ./../../..
8
9
require (
10
- github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364
10
+ github.com/ipfs/boxo v0.34.1-0.20250909170220-e69f67e94c11
11
github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
12
github.com/libp2p/go-libp2p v0.43.0
13
github.com/multiformats/go-multiaddr v0.16.1
docs/examples/kubo-as-a-library/go.sum
+2
-2
@@ -287,8 +287,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd
287
github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU=
288
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
289
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
290
-github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364 h1:VdRdPlosNYdlENC0UsCxapHala/Q1Me6yBY5ChKUw7s=
291
-github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
290
+github.com/ipfs/boxo v0.34.1-0.20250909170220-e69f67e94c11 h1:EsCbOKE+giLtrFTysnbTzIRQENOiLdcpOY3kV3y6wlU=
291
+github.com/ipfs/boxo v0.34.1-0.20250909170220-e69f67e94c11/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
292
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
293
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
294
github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk=
gc/gc.go
+1
-1
@@ -165,7 +165,7 @@ func Descendants(ctx context.Context, getLinks dag.GetLinks, set *cid.Set, roots
165
}
166
167
verboseCidError := func(err error) error {
168
- if strings.Contains(err.Error(), verifcid.ErrBelowMinimumHashLength.Error()) ||
168
+ if strings.Contains(err.Error(), verifcid.ErrDigestTooSmall.Error()) ||
169
strings.Contains(err.Error(), verifcid.ErrPossiblyInsecureHashFunction.Error()) {
170
err = fmt.Errorf("\"%s\"\nPlease run 'ipfs pin verify'"+ // nolint
171
" to list insecure hashes. If you want to read them,"+
go.mod
+1
-1
@@ -22,7 +22,7 @@ require (
22
github.com/hashicorp/go-version v1.7.0
23
github.com/ipfs-shipyard/nopfs v0.0.14
24
github.com/ipfs-shipyard/nopfs/ipfs v0.25.0
25
- github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364
25
+ github.com/ipfs/boxo v0.34.1-0.20250909170220-e69f67e94c11
26
github.com/ipfs/go-block-format v0.2.2
27
github.com/ipfs/go-cid v0.5.0
28
github.com/ipfs/go-cidutil v0.1.0
go.sum
+2
-2
@@ -354,8 +354,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd
354
github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU=
355
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
356
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
357
-github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364 h1:VdRdPlosNYdlENC0UsCxapHala/Q1Me6yBY5ChKUw7s=
358
-github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
357
+github.com/ipfs/boxo v0.34.1-0.20250909170220-e69f67e94c11 h1:EsCbOKE+giLtrFTysnbTzIRQENOiLdcpOY3kV3y6wlU=
358
+github.com/ipfs/boxo v0.34.1-0.20250909170220-e69f67e94c11/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
359
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
360
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
361
github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk=
test/cli/identity_cid_test.go
new
+310
@@ -0,0 +1,310 @@
1
+package cli
2
+
3
+import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+
10
+ "github.com/ipfs/boxo/verifcid"
11
+ "github.com/ipfs/kubo/config"
12
+ "github.com/ipfs/kubo/test/cli/harness"
13
+ "github.com/stretchr/testify/assert"
14
+ "github.com/stretchr/testify/require"
15
+)
16
+
17
+func TestIdentityCIDOverflowProtection(t *testing.T) {
18
+ t.Parallel()
19
+
20
+ t.Run("ipfs add --hash=identity with small data succeeds", func(t *testing.T) {
21
+ t.Parallel()
22
+ node := harness.NewT(t).NewNode().Init().StartDaemon()
23
+ defer node.StopDaemon()
24
+
25
+ // small data that fits in identity CID
26
+ smallData := "small data"
27
+ tempFile := filepath.Join(node.Dir, "small.txt")
28
+ err := os.WriteFile(tempFile, []byte(smallData), 0644)
29
+ require.NoError(t, err)
30
+
31
+ res := node.IPFS("add", "--hash=identity", tempFile)
32
+ assert.NoError(t, res.Err)
33
+ cid := strings.Fields(res.Stdout.String())[1]
34
+
35
+ // verify it's actually using identity hash
36
+ res = node.IPFS("cid", "format", "-f", "%h", cid)
37
+ assert.NoError(t, res.Err)
38
+ assert.Equal(t, "identity", res.Stdout.Trimmed())
39
+ })
40
+
41
+ t.Run("ipfs add --hash=identity with large data fails", func(t *testing.T) {
42
+ t.Parallel()
43
+ node := harness.NewT(t).NewNode().Init().StartDaemon()
44
+ defer node.StopDaemon()
45
+
46
+ // data larger than verifcid.DefaultMaxIdentityDigestSize
47
+ largeData := strings.Repeat("x", verifcid.DefaultMaxIdentityDigestSize+50)
48
+ tempFile := filepath.Join(node.Dir, "large.txt")
49
+ err := os.WriteFile(tempFile, []byte(largeData), 0644)
50
+ require.NoError(t, err)
51
+
52
+ res := node.RunIPFS("add", "--hash=identity", tempFile)
53
+ assert.NotEqual(t, 0, res.ExitErr.ExitCode())
54
+ // should error with digest too large message
55
+ assert.Contains(t, res.Stderr.String(), "digest too large")
56
+ })
57
+
58
+ t.Run("ipfs add --inline with valid --inline-limit succeeds", func(t *testing.T) {
59
+ t.Parallel()
60
+ node := harness.NewT(t).NewNode().Init().StartDaemon()
61
+ defer node.StopDaemon()
62
+
63
+ smallData := "small inline data"
64
+ tempFile := filepath.Join(node.Dir, "inline.txt")
65
+ err := os.WriteFile(tempFile, []byte(smallData), 0644)
66
+ require.NoError(t, err)
67
+
68
+ // use limit just under the maximum
69
+ limit := verifcid.DefaultMaxIdentityDigestSize - 10
70
+ res := node.IPFS("add", "--inline", fmt.Sprintf("--inline-limit=%d", limit), tempFile)
71
+ assert.NoError(t, res.Err)
72
+ cid := strings.Fields(res.Stdout.String())[1]
73
+
74
+ // verify the CID is using identity hash (inline)
75
+ res = node.IPFS("cid", "format", "-f", "%h", cid)
76
+ assert.NoError(t, res.Err)
77
+ assert.Equal(t, "identity", res.Stdout.Trimmed())
78
+
79
+ // verify the codec (may be dag-pb or raw depending on kubo version)
80
+ res = node.IPFS("cid", "format", "-f", "%c", cid)
81
+ assert.NoError(t, res.Err)
82
+ // Accept either raw or dag-pb as both are valid for inline data
83
+ codec := res.Stdout.Trimmed()
84
+ assert.True(t, codec == "raw" || codec == "dag-pb", "expected raw or dag-pb codec, got %s", codec)
85
+ })
86
+
87
+ t.Run("ipfs add --inline with excessive --inline-limit fails", func(t *testing.T) {
88
+ t.Parallel()
89
+ node := harness.NewT(t).NewNode().Init().StartDaemon()
90
+ defer node.StopDaemon()
91
+
92
+ smallData := "data"
93
+ tempFile := filepath.Join(node.Dir, "inline2.txt")
94
+ err := os.WriteFile(tempFile, []byte(smallData), 0644)
95
+ require.NoError(t, err)
96
+
97
+ excessiveLimit := verifcid.DefaultMaxIdentityDigestSize + 50
98
+ res := node.RunIPFS("add", "--inline", fmt.Sprintf("--inline-limit=%d", excessiveLimit), tempFile)
99
+ assert.NotEqual(t, 0, res.ExitErr.ExitCode())
100
+ assert.Contains(t, res.Stderr.String(), fmt.Sprintf("inline-limit %d exceeds maximum allowed size of %d bytes", excessiveLimit, verifcid.DefaultMaxIdentityDigestSize))
101
+ })
102
+
103
+ t.Run("ipfs files write --hash=identity appending to identity CID switches to configured hash", func(t *testing.T) {
104
+ t.Parallel()
105
+ node := harness.NewT(t).NewNode().Init().StartDaemon()
106
+ defer node.StopDaemon()
107
+
108
+ // create initial small file with identity CID
109
+ initialData := "initial"
110
+ tempFile := filepath.Join(node.Dir, "initial.txt")
111
+ err := os.WriteFile(tempFile, []byte(initialData), 0644)
112
+ require.NoError(t, err)
113
+
114
+ res := node.IPFS("add", "--hash=identity", tempFile)
115
+ assert.NoError(t, res.Err)
116
+ cid1 := strings.Fields(res.Stdout.String())[1]
117
+
118
+ // verify initial CID uses identity
119
+ res = node.IPFS("cid", "format", "-f", "%h", cid1)
120
+ assert.NoError(t, res.Err)
121
+ assert.Equal(t, "identity", res.Stdout.Trimmed())
122
+
123
+ // copy to MFS
124
+ res = node.IPFS("files", "cp", fmt.Sprintf("/ipfs/%s", cid1), "/identity-file")
125
+ assert.NoError(t, res.Err)
126
+
127
+ // append data that would exceed identity CID limit
128
+ appendData := strings.Repeat("a", verifcid.DefaultMaxIdentityDigestSize)
129
+ appendFile := filepath.Join(node.Dir, "append.txt")
130
+ err = os.WriteFile(appendFile, []byte(appendData), 0644)
131
+ require.NoError(t, err)
132
+
133
+ // append to the end of the file
134
+ // get the current data size
135
+ res = node.IPFS("files", "stat", "--format", "<size>", "/identity-file")
136
+ assert.NoError(t, res.Err)
137
+ size := res.Stdout.Trimmed()
138
+ // this should succeed because DagModifier in boxo handles the overflow
139
+ res = node.IPFS("files", "write", "--hash=identity", "--offset="+size, "/identity-file", appendFile)
140
+ assert.NoError(t, res.Err)
141
+
142
+ // check that the file now uses non-identity hash
143
+ res = node.IPFS("files", "stat", "--hash", "/identity-file")
144
+ assert.NoError(t, res.Err)
145
+ newCid := res.Stdout.Trimmed()
146
+
147
+ // verify new CID does NOT use identity
148
+ res = node.IPFS("cid", "format", "-f", "%h", newCid)
149
+ assert.NoError(t, res.Err)
150
+ assert.NotEqual(t, "identity", res.Stdout.Trimmed())
151
+
152
+ // verify it switched to a cryptographic hash
153
+ assert.Equal(t, config.DefaultHashFunction, res.Stdout.Trimmed())
154
+ })
155
+
156
+ t.Run("ipfs files write --hash=identity with small write creates identity CID", func(t *testing.T) {
157
+ t.Parallel()
158
+ node := harness.NewT(t).NewNode().Init().StartDaemon()
159
+ defer node.StopDaemon()
160
+
161
+ // create a small file with identity hash directly in MFS
162
+ smallData := "small"
163
+ tempFile := filepath.Join(node.Dir, "small.txt")
164
+ err := os.WriteFile(tempFile, []byte(smallData), 0644)
165
+ require.NoError(t, err)
166
+
167
+ // write to MFS with identity hash
168
+ res := node.IPFS("files", "write", "--create", "--hash=identity", "/mfs-identity", tempFile)
169
+ assert.NoError(t, res.Err)
170
+
171
+ // verify using identity CID
172
+ res = node.IPFS("files", "stat", "--hash", "/mfs-identity")
173
+ assert.NoError(t, res.Err)
174
+ cid := res.Stdout.Trimmed()
175
+
176
+ // verify CID uses identity hash
177
+ res = node.IPFS("cid", "format", "-f", "%h", cid)
178
+ assert.NoError(t, res.Err)
179
+ assert.Equal(t, "identity", res.Stdout.Trimmed())
180
+
181
+ // verify content
182
+ res = node.IPFS("files", "read", "/mfs-identity")
183
+ assert.NoError(t, res.Err)
184
+ assert.Equal(t, smallData, res.Stdout.Trimmed())
185
+ })
186
+
187
+ t.Run("raw node with identity CID converts to UnixFS when appending", func(t *testing.T) {
188
+ t.Parallel()
189
+ node := harness.NewT(t).NewNode().Init().StartDaemon()
190
+ defer node.StopDaemon()
191
+
192
+ // create raw block with identity CID
193
+ rawData := "raw"
194
+ tempFile := filepath.Join(node.Dir, "raw.txt")
195
+ err := os.WriteFile(tempFile, []byte(rawData), 0644)
196
+ require.NoError(t, err)
197
+
198
+ res := node.IPFS("block", "put", "--format=raw", "--mhtype=identity", tempFile)
199
+ assert.NoError(t, res.Err)
200
+ rawCid := res.Stdout.Trimmed()
201
+
202
+ // verify initial CID uses identity hash and raw codec
203
+ res = node.IPFS("cid", "format", "-f", "%h", rawCid)
204
+ assert.NoError(t, res.Err)
205
+ assert.Equal(t, "identity", res.Stdout.Trimmed())
206
+
207
+ res = node.IPFS("cid", "format", "-f", "%c", rawCid)
208
+ assert.NoError(t, res.Err)
209
+ assert.Equal(t, "raw", res.Stdout.Trimmed())
210
+
211
+ // copy to MFS
212
+ res = node.IPFS("files", "cp", fmt.Sprintf("/ipfs/%s", rawCid), "/raw-identity")
213
+ assert.NoError(t, res.Err)
214
+
215
+ // append data
216
+ appendData := "appended"
217
+ appendFile := filepath.Join(node.Dir, "append-raw.txt")
218
+ err = os.WriteFile(appendFile, []byte(appendData), 0644)
219
+ require.NoError(t, err)
220
+
221
+ // get current data size for appending
222
+ res = node.IPFS("files", "stat", "--format", "<size>", "/raw-identity")
223
+ assert.NoError(t, res.Err)
224
+ size := res.Stdout.Trimmed()
225
+ res = node.IPFS("files", "write", "--hash=identity", "--offset="+size, "/raw-identity", appendFile)
226
+ assert.NoError(t, res.Err)
227
+
228
+ // verify content
229
+ res = node.IPFS("files", "read", "/raw-identity")
230
+ assert.NoError(t, res.Err)
231
+ assert.Equal(t, rawData+appendData, res.Stdout.Trimmed())
232
+
233
+ // check that it's now a UnixFS structure (dag-pb)
234
+ res = node.IPFS("files", "stat", "--hash", "/raw-identity")
235
+ assert.NoError(t, res.Err)
236
+ newCid := res.Stdout.Trimmed()
237
+
238
+ res = node.IPFS("cid", "format", "-f", "%c", newCid)
239
+ assert.NoError(t, res.Err)
240
+ assert.Equal(t, "dag-pb", res.Stdout.Trimmed())
241
+
242
+ res = node.IPFS("files", "stat", "/raw-identity")
243
+ assert.NoError(t, res.Err)
244
+ assert.Contains(t, res.Stdout.String(), "Type: file")
245
+ })
246
+
247
+ t.Run("ipfs add --inline-limit at exactly max size succeeds", func(t *testing.T) {
248
+ t.Parallel()
249
+ node := harness.NewT(t).NewNode().Init().StartDaemon()
250
+ defer node.StopDaemon()
251
+
252
+ // create small data that will be inlined
253
+ smallData := "test data for inline"
254
+ tempFile := filepath.Join(node.Dir, "exact.txt")
255
+ err := os.WriteFile(tempFile, []byte(smallData), 0644)
256
+ require.NoError(t, err)
257
+
258
+ // exactly at the limit should succeed
259
+ res := node.IPFS("add", "--inline", fmt.Sprintf("--inline-limit=%d", verifcid.DefaultMaxIdentityDigestSize), tempFile)
260
+ assert.NoError(t, res.Err)
261
+ cid := strings.Fields(res.Stdout.String())[1]
262
+
263
+ // verify it uses identity hash (inline) since data is small enough
264
+ res = node.IPFS("cid", "format", "-f", "%h", cid)
265
+ assert.NoError(t, res.Err)
266
+ assert.Equal(t, "identity", res.Stdout.Trimmed())
267
+ })
268
+
269
+ t.Run("ipfs add --inline-limit one byte over max fails", func(t *testing.T) {
270
+ t.Parallel()
271
+ node := harness.NewT(t).NewNode().Init().StartDaemon()
272
+ defer node.StopDaemon()
273
+
274
+ smallData := "test"
275
+ tempFile := filepath.Join(node.Dir, "oneover.txt")
276
+ err := os.WriteFile(tempFile, []byte(smallData), 0644)
277
+ require.NoError(t, err)
278
+
279
+ // one byte over should fail
280
+ overLimit := verifcid.DefaultMaxIdentityDigestSize + 1
281
+ res := node.RunIPFS("add", "--inline", fmt.Sprintf("--inline-limit=%d", overLimit), tempFile)
282
+ assert.NotEqual(t, 0, res.ExitErr.ExitCode())
283
+ assert.Contains(t, res.Stderr.String(), fmt.Sprintf("inline-limit %d exceeds maximum allowed size of %d bytes", overLimit, verifcid.DefaultMaxIdentityDigestSize))
284
+ })
285
+
286
+ t.Run("ipfs add --inline with data larger than limit uses configured hash", func(t *testing.T) {
287
+ t.Parallel()
288
+ node := harness.NewT(t).NewNode().Init().StartDaemon()
289
+ defer node.StopDaemon()
290
+
291
+ // data larger than inline limit
292
+ largeData := strings.Repeat("y", 100)
293
+ tempFile := filepath.Join(node.Dir, "toolarge.txt")
294
+ err := os.WriteFile(tempFile, []byte(largeData), 0644)
295
+ require.NoError(t, err)
296
+
297
+ // set inline limit smaller than data
298
+ res := node.IPFS("add", "--inline", "--inline-limit=50", tempFile)
299
+ assert.NoError(t, res.Err)
300
+ cid := strings.Fields(res.Stdout.String())[1]
301
+
302
+ // verify it's NOT using identity hash (data too large for inline)
303
+ res = node.IPFS("cid", "format", "-f", "%h", cid)
304
+ assert.NoError(t, res.Err)
305
+ assert.NotEqual(t, "identity", res.Stdout.Trimmed())
306
+
307
+ // should use configured hash
308
+ assert.Equal(t, config.DefaultHashFunction, res.Stdout.Trimmed())
309
+ })
310
+}
test/dependencies/go.mod
+1
-1
@@ -134,7 +134,7 @@ require (
134
github.com/huin/goupnp v1.3.0 // indirect
135
github.com/inconshreveable/mousetrap v1.1.0 // indirect
136
github.com/ipfs/bbloom v0.0.4 // indirect
137
- github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364 // indirect
137
+ github.com/ipfs/boxo v0.34.1-0.20250909170220-e69f67e94c11 // indirect
138
github.com/ipfs/go-bitfield v1.1.0 // indirect
139
github.com/ipfs/go-block-format v0.2.2 // indirect
140
github.com/ipfs/go-cid v0.5.0 // indirect
test/dependencies/go.sum
+2
-2
@@ -332,8 +332,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
332
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
333
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
334
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
335
-github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364 h1:VdRdPlosNYdlENC0UsCxapHala/Q1Me6yBY5ChKUw7s=
336
-github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
335
+github.com/ipfs/boxo v0.34.1-0.20250909170220-e69f67e94c11 h1:EsCbOKE+giLtrFTysnbTzIRQENOiLdcpOY3kV3y6wlU=
336
+github.com/ipfs/boxo v0.34.1-0.20250909170220-e69f67e94c11/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
337
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
338
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
339
github.com/ipfs/go-block-format v0.2.2 h1:uecCTgRwDIXyZPgYspaLXoMiMmxQpSx2aq34eNc4YvQ=
test/sharness/t0275-cid-security.sh
+4
-4
@@ -15,7 +15,7 @@ test_expect_success "adding using unsafe function fails with error" '
15
'
16
17
test_expect_success "error reason is pointed out" '
18
- grep "insecure hash functions not allowed" add_out || test_fsh cat add_out
18
+ grep "potentially insecure hash functions not allowed" add_out || test_fsh cat add_out
19
'
20
21
test_expect_success "adding using too short of a hash function gives out an error" '
@@ -23,7 +23,7 @@ test_expect_success "adding using too short of a hash function gives out an erro
23
'
24
25
test_expect_success "error reason is pointed out" '
26
- grep "hashes must be at least 20 bytes long" block_out
26
+ grep "digest too small" block_out
27
'
28
29
@@ -35,7 +35,7 @@ test_cat_get() {
35
36
37
test_expect_success "error reason is pointed out" '
38
- grep "insecure hash functions not allowed" ipfs_cat
38
+ grep "potentially insecure hash functions not allowed" ipfs_cat
39
'
40
41
@@ -45,7 +45,7 @@ test_cat_get() {
45
'
46
47
test_expect_success "error reason is pointed out" '
48
- grep "hashes must be at least 20 bytes long" ipfs_get
48
+ grep "digest too small" ipfs_get
49
'
50
}
51