-
Notifications
You must be signed in to change notification settings - Fork 0
refactor(#249): add caller-callee contract consistency heuristic #283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -104,6 +104,60 @@ Policy thresholds: | |
| broader access than when the value is correctly set, the code is | ||
| fail-open. | ||
|
|
||
| ## Caller-callee contract consistency | ||
|
|
||
| **Category:** Use `fail-open` for all findings in this section. | ||
|
|
||
| When a function receives a security-relevant field via a struct parameter | ||
| (e.g., an allowlist, ACL, permission set, or scope list) and applies a | ||
| permissive local default for internal use — such as | ||
| `localVar := opts.Allowlist; if localVar == nil { localVar = defaults }` | ||
| — without writing the default back to the struct, check whether the | ||
| caller passes the same struct to downstream functions that interpret the | ||
| field differently. | ||
|
|
||
| This is a cross-function fail-open pattern: the callee behaves as if the | ||
| field has a permissive default, but the struct still carries the original | ||
| nil/absent value. If a subsequent callee treats nil as deny-all while the | ||
| first callee treated nil as use-defaults, the two operations within the | ||
| same code path apply contradictory security semantics. | ||
|
|
||
| **Heuristic:** When a function in the diff locally defaults a | ||
| security-relevant struct field without writing it back: | ||
|
|
||
| 1. **Identify the struct parameter** and the field being defaulted. | ||
| 2. **Trace the struct through the caller.** Find where the caller | ||
| obtained the struct and what other functions receive it after the | ||
| current function returns. | ||
| 3. **Check nil/absent semantics in each downstream callee.** If any | ||
| downstream function interprets nil/absent for the same field as | ||
| deny-all (or any semantics different from the local default), flag | ||
| the inconsistency. | ||
| 4. **Assess severity.** If the field controls a security boundary | ||
| (allowlists, permission sets, access scopes), severity is at least | ||
| **medium**. Distinguish the two directions of inconsistency: | ||
| - **Deny-then-permit** (first callee restricts, subsequent callee | ||
| defaults to permissive): this is a genuine **fail-open** path — | ||
| severity is **high**. | ||
| - **Permit-then-deny** (first callee permits, subsequent callee | ||
| defaults to deny-all): this is a service-disruption / functional | ||
| correctness bug, not a security fail-open. Use category | ||
| `logic-error` at **medium** severity. | ||
|
|
||
| **Do not stop analysis at the function boundary.** The pattern is only | ||
| visible when you trace the struct through the caller's subsequent | ||
| operations. A function that locally defaults a field may appear safe in | ||
| isolation but create an asymmetry that breaks downstream invariants. | ||
|
|
||
| Edge cases that do NOT require a finding: | ||
|
|
||
| - The function writes the default back to the struct field (the caller | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [low] edge-case The edge-case exclusion for 'explicitly empty value (e.g., empty slice, not nil)' relies on the nil-vs-empty-slice distinction, which is a Go idiom. In languages without this type-level distinction (Python None vs [], JavaScript null vs undefined), the heuristic may not map cleanly. Acceptable for the primary Go use case but may produce false negatives in polyglot reviews. |
||
| sees the updated value). | ||
| - The struct is not passed to any other function after the call. | ||
| - The field is an explicitly empty value (e.g., empty slice, not nil) | ||
| rather than absent/nil — this signals intentional "no entries" rather | ||
| than "not configured." | ||
|
|
||
| ## Permission and role changes | ||
|
|
||
| **Categories:** `permission-expansion`, `permission-reduction`, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[low] terminology-consistency
The section header 'Caller-callee contract consistency' introduces terminology not used elsewhere in the skill files. However, 'caller-callee contract' is a well-established software engineering term and is internally consistent across both changed files.