@@ -973,6 +973,9 @@ fn is_capability_allowed(capability: &Capability, ceiling: &CapabilityCeiling) -
973973 "time.now" => !capability. value . is_empty ( ) && ceiling. time . unwrap_or ( false ) ,
974974 "random.bytes" => !capability. value . is_empty ( ) && ceiling. random . unwrap_or ( false ) ,
975975 "kv.read" => {
976+ if capability. value . is_empty ( ) {
977+ return false ;
978+ }
976979 let Some ( kv) = & ceiling. kv else {
977980 return false ;
978981 } ;
@@ -984,6 +987,9 @@ fn is_capability_allowed(capability: &Capability, ceiling: &CapabilityCeiling) -
984987 . any ( |item| item == "*" || item == & capability. value )
985988 }
986989 "kv.write" => {
990+ if capability. value . is_empty ( ) {
991+ return false ;
992+ }
987993 let Some ( kv) = & ceiling. kv else {
988994 return false ;
989995 } ;
@@ -995,6 +1001,9 @@ fn is_capability_allowed(capability: &Capability, ceiling: &CapabilityCeiling) -
9951001 . any ( |item| item == "*" || item == & capability. value )
9961002 }
9971003 "queue.publish" => {
1004+ if capability. value . is_empty ( ) {
1005+ return false ;
1006+ }
9981007 let Some ( queue) = & ceiling. queue else {
9991008 return false ;
10001009 } ;
@@ -1006,6 +1015,9 @@ fn is_capability_allowed(capability: &Capability, ceiling: &CapabilityCeiling) -
10061015 . any ( |item| item == "*" || item == & capability. value )
10071016 }
10081017 "queue.consume" => {
1018+ if capability. value . is_empty ( ) {
1019+ return false ;
1020+ }
10091021 let Some ( queue) = & ceiling. queue else {
10101022 return false ;
10111023 } ;
@@ -1171,13 +1183,23 @@ fn validate_policy_constraints(policy: &Policy) -> Result<(), VerifyError> {
11711183 }
11721184 if let Some ( kv) = & ceiling. kv {
11731185 if let Some ( read) = & kv. read {
1186+ if read. iter ( ) . any ( String :: is_empty) {
1187+ return Err ( VerifyError :: PolicyConstraint (
1188+ "capability_ceiling.kv.read items must be non-empty" . to_string ( ) ,
1189+ ) ) ;
1190+ }
11741191 if has_duplicates ( read) {
11751192 return Err ( VerifyError :: PolicyConstraint (
11761193 "capability_ceiling.kv.read items must be unique" . to_string ( ) ,
11771194 ) ) ;
11781195 }
11791196 }
11801197 if let Some ( write) = & kv. write {
1198+ if write. iter ( ) . any ( String :: is_empty) {
1199+ return Err ( VerifyError :: PolicyConstraint (
1200+ "capability_ceiling.kv.write items must be non-empty" . to_string ( ) ,
1201+ ) ) ;
1202+ }
11811203 if has_duplicates ( write) {
11821204 return Err ( VerifyError :: PolicyConstraint (
11831205 "capability_ceiling.kv.write items must be unique" . to_string ( ) ,
@@ -1187,13 +1209,23 @@ fn validate_policy_constraints(policy: &Policy) -> Result<(), VerifyError> {
11871209 }
11881210 if let Some ( queue) = & ceiling. queue {
11891211 if let Some ( publish) = & queue. publish {
1212+ if publish. iter ( ) . any ( String :: is_empty) {
1213+ return Err ( VerifyError :: PolicyConstraint (
1214+ "capability_ceiling.queue.publish items must be non-empty" . to_string ( ) ,
1215+ ) ) ;
1216+ }
11901217 if has_duplicates ( publish) {
11911218 return Err ( VerifyError :: PolicyConstraint (
11921219 "capability_ceiling.queue.publish items must be unique" . to_string ( ) ,
11931220 ) ) ;
11941221 }
11951222 }
11961223 if let Some ( consume) = & queue. consume {
1224+ if consume. iter ( ) . any ( String :: is_empty) {
1225+ return Err ( VerifyError :: PolicyConstraint (
1226+ "capability_ceiling.queue.consume items must be non-empty" . to_string ( ) ,
1227+ ) ) ;
1228+ }
11971229 if has_duplicates ( consume) {
11981230 return Err ( VerifyError :: PolicyConstraint (
11991231 "capability_ceiling.queue.consume items must be unique" . to_string ( ) ,
@@ -1756,6 +1788,68 @@ capability_ceiling:
17561788 ) ) ;
17571789 }
17581790
1791+ #[ test]
1792+ fn denies_empty_kv_and_queue_capability_values_even_with_wildcards ( ) {
1793+ let policy = Policy {
1794+ version : 1 ,
1795+ trusted_signers : vec ! [ "alice.dev" . to_string( ) ] ,
1796+ capability_ceiling : CapabilityCeiling {
1797+ fs : None ,
1798+ net : None ,
1799+ kv : Some ( PolicyKv {
1800+ read : Some ( vec ! [ "*" . to_string( ) ] ) ,
1801+ write : Some ( vec ! [ "*" . to_string( ) ] ) ,
1802+ } ) ,
1803+ queue : Some ( PolicyQueue {
1804+ publish : Some ( vec ! [ "*" . to_string( ) ] ) ,
1805+ consume : Some ( vec ! [ "*" . to_string( ) ] ) ,
1806+ } ) ,
1807+ env : None ,
1808+ exec : Some ( false ) ,
1809+ time : Some ( false ) ,
1810+ random : Some ( false ) ,
1811+ } ,
1812+ } ;
1813+ let requested = vec ! [
1814+ Capability {
1815+ kind: "kv.read" . to_string( ) ,
1816+ value: String :: new( ) ,
1817+ } ,
1818+ Capability {
1819+ kind: "kv.write" . to_string( ) ,
1820+ value: String :: new( ) ,
1821+ } ,
1822+ Capability {
1823+ kind: "queue.publish" . to_string( ) ,
1824+ value: String :: new( ) ,
1825+ } ,
1826+ Capability {
1827+ kind: "queue.consume" . to_string( ) ,
1828+ value: String :: new( ) ,
1829+ } ,
1830+ ] ;
1831+ assert ! ( matches!(
1832+ enforce_capability_ceiling( & requested, & policy) ,
1833+ Err ( VerifyError :: CapabilityDenied ( _) )
1834+ ) ) ;
1835+ }
1836+
1837+ #[ test]
1838+ fn denies_policy_with_empty_kv_or_queue_entries ( ) {
1839+ let raw = br#"{
1840+ "version": 1,
1841+ "trusted_signers": ["alice.dev"],
1842+ "capability_ceiling": {
1843+ "kv": { "read": [""] },
1844+ "queue": { "publish": ["*"] }
1845+ }
1846+ }"# ;
1847+ assert ! ( matches!(
1848+ parse_policy_document( raw) ,
1849+ Err ( VerifyError :: PolicyConstraint ( _) )
1850+ ) ) ;
1851+ }
1852+
17591853 #[ test]
17601854 fn denies_policy_net_prefix_with_query_or_fragment ( ) {
17611855 let raw = br#"{
0 commit comments