<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Bucket Policy on SILO</title>
    <link>https://silo.pgsty.com/tags/bucket-policy/</link>
    <description>Recent content in Bucket Policy on SILO</description>
    <generator>Hugo</generator>
    <language>en</language>
    
    
    
      <lastBuildDate>Sat, 24 Oct 2026 18:19:49 +0800</lastBuildDate>
    
    
      <atom:link href="https://silo.pgsty.com/tags/bucket-policy/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
        <title>SN-2026-003: s3:versionid Condition Evaluation and Request Condition Hardening</title>
        <link>https://silo.pgsty.com/blog/security/s3-versionid-conditions/</link>
        <pubDate>Tue, 04 Aug 2026 00:00:00 +0000</pubDate>
        
        <guid>https://silo.pgsty.com/blog/security/s3-versionid-conditions/</guid>
        <description>&lt;p&gt;&lt;strong&gt;&lt;code&gt;SN-2026-003&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;SN-2026-005&lt;/code&gt;&lt;/strong&gt; cover a set of S3 IAM policy evaluation fixes shipped in &lt;a href=&#34;https://github.com/pgsty/silo/releases/tag/RELEASE.2026-08-04T00-00-00Z&#34;&gt;Server 20260804&lt;/a&gt; (&lt;code&gt;80c2c9d03&lt;/code&gt;, &lt;code&gt;2f55347f7&lt;/code&gt;, &lt;code&gt;eee05a17c&lt;/code&gt;, and &lt;a href=&#34;https://github.com/pgsty/silo-pkg/releases/tag/v3.12.0&#34;&gt;silo-pkg &lt;code&gt;v3.12.0&lt;/code&gt;&lt;/a&gt; commit &lt;code&gt;2b087a1&lt;/code&gt;; &lt;a href=&#34;https://github.com/pgsty/silo/issues/40&#34;&gt;&lt;code&gt;pgsty/silo#40&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/pgsty/silo/issues/53&#34;&gt;&lt;code&gt;pgsty/silo#53&lt;/code&gt;&lt;/a&gt;):&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;SN-2026-003&lt;/code&gt; (&lt;code&gt;80c2c9d03&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;getConditionValues&lt;/code&gt; populated &lt;code&gt;args.ConditionValues[&amp;quot;versionid&amp;quot;] = []string{&amp;quot;&amp;quot;}&lt;/code&gt; even when a request carried no &lt;code&gt;versionId&lt;/code&gt;, causing IAM &lt;code&gt;Null&lt;/code&gt; conditions on &lt;code&gt;s3:versionid&lt;/code&gt; to evaluate as if the key were always present; fixing that alone would have opened a &lt;code&gt;DeleteObjects&lt;/code&gt; XML &lt;code&gt;&amp;lt;VersionId&amp;gt;&lt;/code&gt; bypass without per-entry rebinding in &lt;code&gt;authorizeRequestWithTags&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Condition-source isolation (&lt;code&gt;2f55347f7&lt;/code&gt;)&lt;/strong&gt;: Separated query parameters, HTTP headers, internal server keys, and &lt;code&gt;s3:ExistingObjectTag/&amp;lt;key&amp;gt;&lt;/code&gt; vs. &lt;code&gt;s3:RequestObjectTag/&amp;lt;key&amp;gt;&lt;/code&gt; so untrusted query strings or headers cannot spoof internal condition keys.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;SN-2026-005&lt;/code&gt; (&lt;code&gt;2b087a1&lt;/code&gt; / &lt;code&gt;eee05a17c&lt;/code&gt;)&lt;/strong&gt;: Added &lt;code&gt;ParseConfigStrict&lt;/code&gt; to reject degenerate bare resource prefixes (&lt;code&gt;&amp;quot;arn:aws:s3:::&amp;quot;&lt;/code&gt; and &lt;code&gt;&amp;quot;*arn:aws:s3:::&amp;quot;&lt;/code&gt;) when writing IAM and bucket policies.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Conclusions first&#xA;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Unconditional empty slice inverted &lt;code&gt;Null&lt;/code&gt; checks&lt;/strong&gt;: In &lt;code&gt;silo-pkg&lt;/code&gt;, &lt;code&gt;nullFunc.evaluate&lt;/code&gt; tests whether a condition key is absent by checking &lt;code&gt;len(rvalues) == 0&lt;/code&gt;. Because &lt;code&gt;getConditionValues&lt;/code&gt; unconditionally assigned &lt;code&gt;args.ConditionValues[&amp;quot;versionid&amp;quot;] = []string{vid}&lt;/code&gt; (producing &lt;code&gt;[]string{&amp;quot;&amp;quot;}&lt;/code&gt; when &lt;code&gt;vid == &amp;quot;&amp;quot;&lt;/code&gt;), &lt;code&gt;len(rvalues)&lt;/code&gt; was always &lt;code&gt;1&lt;/code&gt;—making &lt;code&gt;Null: {&amp;quot;s3:versionid&amp;quot;: &amp;quot;true&amp;quot;}&lt;/code&gt; always &lt;code&gt;false&lt;/code&gt; and &lt;code&gt;Null: {&amp;quot;s3:versionid&amp;quot;: &amp;quot;false&amp;quot;}&lt;/code&gt; always &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Fixing &lt;code&gt;getConditionValues&lt;/code&gt; alone breaks &lt;code&gt;DeleteObjects&lt;/code&gt;&lt;/strong&gt;: In multi-object &lt;code&gt;DeleteObjects&lt;/code&gt; (&lt;code&gt;POST /?delete&lt;/code&gt;), the target &lt;code&gt;&amp;lt;VersionId&amp;gt;&lt;/code&gt; is inside each XML &lt;code&gt;&amp;lt;Object&amp;gt;&lt;/code&gt; element, not in the URL query string. If &lt;code&gt;getConditionValues&lt;/code&gt; simply omits &lt;code&gt;&amp;quot;versionid&amp;quot;&lt;/code&gt; when the URL query has no &lt;code&gt;versionId&lt;/code&gt;, a policy denying versioned deletes (&lt;code&gt;Deny s3:DeleteObjectVersion&lt;/code&gt; or &lt;code&gt;Condition: {&amp;quot;Null&amp;quot;: {&amp;quot;s3:versionid&amp;quot;: &amp;quot;false&amp;quot;}}&lt;/code&gt;) would fail to match &lt;code&gt;DeleteObjects&lt;/code&gt; XML entries unless &lt;code&gt;authorizeRequestWithTags&lt;/code&gt; rebinds &lt;code&gt;ConditionValues[&amp;quot;versionid&amp;quot;]&lt;/code&gt; from &lt;code&gt;ReqInfo.VersionID&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Whitespace trimming closes a &lt;code&gt;Deny&lt;/code&gt; bypass&lt;/strong&gt;: Version-reading handlers called &lt;code&gt;strings.TrimSpace(r.URL.Query().Get(&amp;quot;versionId&amp;quot;))&lt;/code&gt;, whereas &lt;code&gt;getConditionValues&lt;/code&gt; did not trim whitespace. A request with &lt;code&gt;?versionId=V%20&lt;/code&gt; bypassed &lt;code&gt;Deny StringEquals {&amp;quot;s3:versionid&amp;quot;: &amp;quot;V&amp;quot;}&lt;/code&gt; while still deleting or reading version &lt;code&gt;V&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;the-defect&#34;&gt;Why &lt;code&gt;Null&lt;/code&gt; on &lt;code&gt;s3:versionid&lt;/code&gt; Was Inverted&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;In &lt;code&gt;silo-pkg/bucket/policy/condition/nullfunc.go&lt;/code&gt;, the IAM &lt;code&gt;Null&lt;/code&gt; condition operator checks whether a condition key is missing from the request context:&lt;/p&gt;</description>
      </item>
    <item>
        <title>SN-2026-004: Object-Scoped Policy Grants Reaching Bucket Mutations</title>
        <link>https://silo.pgsty.com/blog/security/object-grant-bucket-reach/</link>
        <pubDate>Tue, 04 Aug 2026 00:00:00 +0000</pubDate>
        
        <guid>https://silo.pgsty.com/blog/security/object-grant-bucket-reach/</guid>
        <description>&lt;p&gt;&lt;strong&gt;&lt;code&gt;SN-2026-004&lt;/code&gt;&lt;/strong&gt; fixes an IAM resource-matching flaw in &lt;a href=&#34;https://github.com/pgsty/silo-pkg/releases/tag/v3.11.0&#34;&gt;silo-pkg &lt;code&gt;v3.11.0&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;3c24ad1&lt;/code&gt;, &lt;code&gt;1f97549&lt;/code&gt;, &lt;code&gt;d8b1fa7&lt;/code&gt;) and &lt;a href=&#34;https://github.com/pgsty/silo/releases/tag/RELEASE.2026-08-04T00-00-00Z&#34;&gt;Server 20260804&lt;/a&gt; (&lt;code&gt;97b7d2804&lt;/code&gt;; upstream reference &lt;a href=&#34;https://github.com/minio/minio/issues/20449&#34;&gt;&lt;code&gt;minio/minio#20449&lt;/code&gt;&lt;/a&gt;): when evaluating bucket-level S3 requests (&lt;code&gt;args.ObjectName == &amp;quot;&amp;quot;&lt;/code&gt;), &lt;code&gt;Statement.IsAllowed&lt;/code&gt; synthesized &lt;code&gt;&amp;quot;bucket/&amp;quot;&lt;/code&gt; as the target resource string, allowing object-scoped wildcard grants such as &lt;code&gt;Allow s3:* on arn:aws:s3:::bucket/*&lt;/code&gt; to authorize sensitive bucket-level mutations including &lt;code&gt;s3:PutBucketPolicy&lt;/code&gt; and &lt;code&gt;s3:DeleteBucket&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Conclusions first&#xA;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Root cause&lt;/strong&gt;: When &lt;code&gt;args.ObjectName&lt;/code&gt; was empty, &lt;code&gt;Statement.IsAllowed&lt;/code&gt; appended &lt;code&gt;&#39;/&#39;&lt;/code&gt; to the bucket name (&lt;code&gt;&amp;quot;bucket/&amp;quot;&lt;/code&gt;), which matched the glob &lt;code&gt;&amp;quot;arn:aws:s3:::bucket/*&amp;quot;&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Privilege escalation&lt;/strong&gt;: A tenant principal given &lt;code&gt;Allow s3:* on arn:aws:s3:::tenant/*&lt;/code&gt; (a common shorthand for &amp;ldquo;all object operations inside this bucket&amp;rdquo;) could call &lt;code&gt;PutBucketPolicy&lt;/code&gt; to grant &lt;code&gt;Principal: &amp;quot;*&amp;quot;&lt;/code&gt; on the bucket, or call &lt;code&gt;DeleteBucket&lt;/code&gt;, &lt;code&gt;PutReplicationConfiguration&lt;/code&gt;, &lt;code&gt;PutBucketLifecycle&lt;/code&gt;, &lt;code&gt;PutBucketVersioning&lt;/code&gt;, or &lt;code&gt;PutBucketObjectLockConfiguration&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Why a targeted fix was required&lt;/strong&gt;: Simply dropping the trailing &lt;code&gt;&#39;/&#39;&lt;/code&gt; for all empty-object checks broke 11 existing STS &lt;code&gt;ListBucket&lt;/code&gt; tests and weakened &lt;code&gt;Deny s3:* on arn:aws:s3:::bucket/*&lt;/code&gt; policies.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Targeted boundary&lt;/strong&gt;: For the 12 sensitive bucket-mutation actions on &lt;code&gt;Allow&lt;/code&gt; statements (&lt;code&gt;!legacyBucketResourceMatch &amp;amp;&amp;amp; statement.Effect == Allow &amp;amp;&amp;amp; isSensitiveBucketMutation(args.Action)&lt;/code&gt;), the resource pattern must match &lt;strong&gt;both&lt;/strong&gt; &lt;code&gt;&amp;quot;bucket&amp;quot;&lt;/code&gt; and &lt;code&gt;&amp;quot;bucket/&amp;quot;&lt;/code&gt;. Setting &lt;code&gt;MINIO_API_LEGACY_BUCKET_RESOURCE_MATCH=on&lt;/code&gt; restores pre-&lt;code&gt;20260804&lt;/code&gt; matching if a deployment temporarily relies on legacy policies.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;the-defect&#34;&gt;Why &lt;code&gt;arn:aws:s3:::bucket/*&lt;/code&gt; Matched Bucket Requests&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;In &lt;code&gt;silo-pkg/bucket/policy/statement.go&lt;/code&gt; (and &lt;code&gt;iam/policy/statement.go&lt;/code&gt;), &lt;code&gt;Statement.IsAllowed(args Args)&lt;/code&gt; constructed the resource string to test against &lt;code&gt;Statement.Resources&lt;/code&gt; as follows:&lt;/p&gt;</description>
      </item>
    
  </channel>
</rss>
