Unknown Successful Response Cache Policy Is Not Globally Enforced
Result
The RFC forbids caching unrecognized successful response codes. The reviewed libcoap proxy observe-cache insertion path does not show a guard that rejects unrecognized 2.xx response codes before storing the response.
Standard Basis
RFC link: RFC 7252 Section 5.6, Caching
Unlike HTTP, the cacheability of CoAP responses does not depend on the
request method, but it depends on the Response Code. The cacheability of each
Response Code is defined along the Response Code definitions in Section 5.9.
Response Codes that indicate success and are unrecognized by an endpoint
MUST NOT be cached.
Source Evidence
Source file: src/coap_proxy.c
obs_opt = coap_check_option(rcvd, COAP_OPTION_OBSERVE, &opt_iter);
/* See if we are doing proxy caching */
if (obs_opt) {
coap_proxy_cache_t *proxy_cache;
coap_cache_key_t *cache_key_l;
/* Need to cache the response */
if (proxy_req->proxy_cache) {
coap_delete_pdu_lkd(proxy_req->proxy_cache->rsp_pdu);
proxy_cache = proxy_req->proxy_cache;
} else {
proxy_cache = coap_malloc_type(COAP_STRING, sizeof(coap_proxy_cache_t));
/* ... */
PROXY_CACHE_ADD(proxy_entry->rsp_cache, proxy_cache);
proxy_cache->ref++;
}
}
Source file: src/coap_cache.c
entry = coap_malloc_type(COAP_CACHE_ENTRY, sizeof(coap_cache_entry_t));
memset(entry, 0, sizeof(coap_cache_entry_t));
entry->session = session;
if (record_pdu == COAP_CACHE_RECORD_PDU) {
entry->pdu = coap_const_pdu_reference_lkd(pdu);
}
entry->cache_key = coap_cache_derive_key(session, pdu, session_based);
HASH_ADD(hh, session->context->cache, cache_key[0], sizeof(coap_cache_key_t), entry);
return entry;
Comparison
The standard requires a negative cacheability decision when a success response code is not recognized. The proxy cache insertion code is driven by the Observe option and proxy-cache state; the reviewed block does not inspect rcvd->code before adding the response to rsp_cache. The generic cache-entry API derives a key and inserts an entry but likewise does not evaluate whether the stored response has an unrecognized success code.
This is a partial mismatch: libcoap provides cache mechanisms, but the audited cache paths do not show a global no-cache guard for unknown successful response codes.
Runtime Reproduction
The static finding was rechecked with a dedicated origin -> libcoap proxy -> two clients runtime harness:
- repro program:
test-libcoap/251-300/repro_id287_unknown_success_cache_runtime.c
- repro binary:
test-libcoap/251-300/repro_id287_unknown_success_cache_runtime_v2.exe
- repro log:
test-libcoap/251-300/repro_id287_unknown_success_cache_runtime.log
Important correction for this runtime check: on this source snapshot reviewed on 2026-05-06, libcoap already defines 2.31 as COAP_RESPONSE_CODE_CONTINUE in include/coap3/coap_pdu.h, so 2.31 is not an unknown success code for this build. To test the RFC condition accurately, the runtime harness used 2.06, which is a success-class code not defined in the reviewed libcoap response-code table.
The harness sets up:
- an origin server resource
/unknown-success returning 2.06 + Observe + Max-Age=90 + payload unknown-success-v1
- the same origin server resource
/known-success returning 2.05 Content + Observe + Max-Age=90 + payload known-success-v1
- a libcoap forward proxy using
coap_resource_proxy_uri_init2() and coap_proxy_forward_request()
- two clients that both send
GET with Observe=0 through the same proxy
The decisive signals are whether the second client receives the cached response and whether the origin hit counter remains 1.
Runtime Log Excerpt
CASE_BEGIN label=unknown proxy_uri=coap://127.0.0.1:5691/unknown-success expected_code=2.06 payload=unknown-success-v1
ORIGIN_HIT label=unknown hit=1 observe=1 code=2.06 payload=unknown-success-v1
CLIENT_RESPONSE name=client1 code=2.06 observe=1 max_age=90 payload=unknown-success-v1
CLIENT_RESPONSE name=client2 code=2.06 observe=1 max_age=90 payload=unknown-success-v1
CASE_RESULT label=unknown client1_code=2.06 client2_code=2.06 client2_payload=unknown-success-v1 origin_hits_after_client2=1 cached=1
unknown_2xx_cached=1 origin_hits_after_client2=1 client2_code=2.06 client2_payload=unknown-success-v1
CASE_BEGIN label=known proxy_uri=coap://127.0.0.1:5693/known-success expected_code=2.05 payload=known-success-v1
ORIGIN_HIT label=known hit=1 observe=1 code=2.05 payload=known-success-v1
CLIENT_RESPONSE name=client1 code=2.05 observe=1 max_age=90 payload=known-success-v1
CLIENT_RESPONSE name=client2 code=2.05 observe=1 max_age=90 payload=known-success-v1
CASE_RESULT label=known client1_code=2.05 client2_code=2.05 client2_payload=known-success-v1 origin_hits_after_client2=1 cached=1
known_205_cached=1 origin_hits_after_client2=1 client2_code=2.05 client2_payload=known-success-v1
Runtime Interpretation
The control case confirms that the proxy observe-cache path is functioning normally for a recognized cacheable success response: known_205_cached=1 and the origin hit counter stays at 1 after the second client request.
The unknown-success case shows the same replay behavior for an unrecognized success-class code: the second client receives 2.06 with the original payload unknown-success-v1, and origin_hits_after_client2 remains 1. That means the response was served from the proxy cache rather than refetched from the origin.
This runtime outcome matches the static review: the libcoap proxy observe-cache machinery caches and replays an unrecognized 2.xx success response instead of enforcing the RFC 7252 Section 5.6 rule that such responses MUST NOT be cached.
Test Result
source_assertions_251_300.py
PASS payload_marker_followed_by_zero_length_rejected
PASS nonzero_payload_encoder_adds_marker
PASS payload_pointer_derived_after_marker
PASS proxy_uri_parser_uses_proxy_mode
PASS uri_split_options_supported
PASS proxy_unavailable_returns_505
PASS proxy_endpoint_authority_can_be_local
PASS delete_unknown_resource_returns_202
PASS bad_option_error_response_path
PASS reserved_response_class_3_accepted
PASS etag_match_large_response_sets_203_without_payload
PASS proxy_forward_response_uses_large_response_helper
PASS proxy_observe_cache_insertion_present
PASS proxy_observe_cache_has_no_response_code_guard
PASS proxy_cleanup_uses_502_not_504
PASS gateway_timeout_code_defined
PASS cn_authority_validation_delegated_to_application
ALL ASSERTIONS PASSED
repro_251_300_protocol_checks.exe
PASS payload marker followed by zero-length payload is rejected
PASS payload marker followed by payload is accepted
PASS payload length is derived from remaining datagram bytes
PASS non-zero payload is encoded with 0xff marker
PASS zero-length payload does not encode payload marker
PASS Proxy-Uri parser accepts HTTP URI for proxy use
PASS origin URI parser rejects proxy-only HTTP URI
PASS origin URI parser splits coap URI authority/path/query
ALL PROTOCOL CHECKS PASSED
repro_id287_unknown_success_cache_runtime_v2.exe
CASE_BEGIN label=unknown proxy_uri=coap://127.0.0.1:5691/unknown-success expected_code=2.06 payload=unknown-success-v1
ORIGIN_HIT label=unknown hit=1 observe=1 code=2.06 payload=unknown-success-v1
CLIENT_RESPONSE name=client1 code=2.06 observe=1 max_age=90 payload=unknown-success-v1
CLIENT_RESPONSE name=client2 code=2.06 observe=1 max_age=90 payload=unknown-success-v1
unknown_2xx_cached=1 origin_hits_after_client2=1 client2_code=2.06 client2_payload=unknown-success-v1
CASE_BEGIN label=known proxy_uri=coap://127.0.0.1:5693/known-success expected_code=2.05 payload=known-success-v1
ORIGIN_HIT label=known hit=1 observe=1 code=2.05 payload=known-success-v1
CLIENT_RESPONSE name=client1 code=2.05 observe=1 max_age=90 payload=known-success-v1
CLIENT_RESPONSE name=client2 code=2.05 observe=1 max_age=90 payload=known-success-v1
known_205_cached=1 origin_hits_after_client2=1 client2_code=2.05 client2_payload=known-success-v1
Unknown Successful Response Cache Policy Is Not Globally Enforced
Result
The RFC forbids caching unrecognized successful response codes. The reviewed libcoap proxy observe-cache insertion path does not show a guard that rejects unrecognized 2.xx response codes before storing the response.
Standard Basis
RFC link: RFC 7252 Section 5.6, Caching
Source Evidence
Source file:
src/coap_proxy.cSource file:
src/coap_cache.cComparison
The standard requires a negative cacheability decision when a success response code is not recognized. The proxy cache insertion code is driven by the Observe option and proxy-cache state; the reviewed block does not inspect
rcvd->codebefore adding the response torsp_cache. The generic cache-entry API derives a key and inserts an entry but likewise does not evaluate whether the stored response has an unrecognized success code.This is a partial mismatch: libcoap provides cache mechanisms, but the audited cache paths do not show a global no-cache guard for unknown successful response codes.
Runtime Reproduction
The static finding was rechecked with a dedicated origin -> libcoap proxy -> two clients runtime harness:
test-libcoap/251-300/repro_id287_unknown_success_cache_runtime.ctest-libcoap/251-300/repro_id287_unknown_success_cache_runtime_v2.exetest-libcoap/251-300/repro_id287_unknown_success_cache_runtime.logImportant correction for this runtime check: on this source snapshot reviewed on 2026-05-06, libcoap already defines
2.31asCOAP_RESPONSE_CODE_CONTINUEininclude/coap3/coap_pdu.h, so2.31is not an unknown success code for this build. To test the RFC condition accurately, the runtime harness used2.06, which is a success-class code not defined in the reviewed libcoap response-code table.The harness sets up:
/unknown-successreturning2.06+Observe+Max-Age=90+ payloadunknown-success-v1/known-successreturning2.05 Content+Observe+Max-Age=90+ payloadknown-success-v1coap_resource_proxy_uri_init2()andcoap_proxy_forward_request()GETwithObserve=0through the same proxyThe decisive signals are whether the second client receives the cached response and whether the origin hit counter remains
1.Runtime Log Excerpt
Runtime Interpretation
The control case confirms that the proxy observe-cache path is functioning normally for a recognized cacheable success response:
known_205_cached=1and the origin hit counter stays at1after the second client request.The unknown-success case shows the same replay behavior for an unrecognized success-class code: the second client receives
2.06with the original payloadunknown-success-v1, andorigin_hits_after_client2remains1. That means the response was served from the proxy cache rather than refetched from the origin.This runtime outcome matches the static review: the libcoap proxy observe-cache machinery caches and replays an unrecognized
2.xxsuccess response instead of enforcing the RFC 7252 Section 5.6 rule that such responsesMUST NOTbe cached.Test Result