<?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>Versioning on SILO</title>
    <link>https://silo.pgsty.com/tags/versioning/</link>
    <description>Recent content in Versioning 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/versioning/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>
    
  </channel>
</rss>
