Skip to content

Commit 35c7b7e

Browse files
authored
Merge pull request #196 from auths-dev/dev-mobileFFIHardening
feat: add mobile ffi hardening at pairing boundaries
2 parents d107f92 + c1ba0db commit 35c7b7e

36 files changed

Lines changed: 4704 additions & 564 deletions

.github/workflows/api-spec.yml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: API spec
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'docs/api-spec.yaml'
8+
- '.spectral.yaml'
9+
- 'crates/auths-pairing-daemon/src/handlers.rs'
10+
- 'crates/auths-pairing-daemon/src/router.rs'
11+
- 'crates/auths-pairing-daemon/src/error.rs'
12+
- '.github/workflows/api-spec.yml'
13+
pull_request:
14+
branches: [main]
15+
paths:
16+
- 'docs/api-spec.yaml'
17+
- '.spectral.yaml'
18+
- 'crates/auths-pairing-daemon/src/handlers.rs'
19+
- 'crates/auths-pairing-daemon/src/router.rs'
20+
- 'crates/auths-pairing-daemon/src/error.rs'
21+
- '.github/workflows/api-spec.yml'
22+
23+
permissions:
24+
contents: read
25+
# `pull-requests: read` needed for the drift-gate label check.
26+
pull-requests: read
27+
28+
jobs:
29+
# ADR 004: `spectral:oas` baseline + project custom rules in
30+
# `.spectral.yaml`. Zero errors; warnings allowed with rationale.
31+
lint:
32+
name: Spectral lint
33+
runs-on: ubuntu-latest
34+
steps:
35+
- uses: actions/checkout@v4
36+
- uses: actions/setup-node@v4
37+
with:
38+
node-version: '20'
39+
- name: Install Spectral
40+
run: npm install -g @stoplight/spectral-cli@6
41+
- name: Lint docs/api-spec.yaml
42+
run: spectral lint docs/api-spec.yaml --fail-severity=error
43+
44+
# ADR 004: if a PR modifies handler/router/error.rs but does not
45+
# touch docs/api-spec.yaml, fail the check. The label
46+
# `api-no-spec-change` (applied by a reviewer) overrides for changes
47+
# that are provably spec-invariant (e.g., internal refactors that
48+
# preserve wire behavior).
49+
drift-gate:
50+
name: Spec drift gate
51+
if: github.event_name == 'pull_request'
52+
runs-on: ubuntu-latest
53+
steps:
54+
- uses: actions/checkout@v4
55+
with:
56+
fetch-depth: 0
57+
- name: Check for spec drift
58+
env:
59+
BASE_REF: ${{ github.base_ref }}
60+
LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
61+
run: |
62+
set -euo pipefail
63+
git fetch origin "$BASE_REF" --depth=1
64+
65+
changed=$(git diff --name-only "origin/$BASE_REF" HEAD)
66+
echo "Changed files in PR:"
67+
echo "$changed"
68+
69+
touched_handler=0
70+
touched_spec=0
71+
while IFS= read -r f; do
72+
case "$f" in
73+
crates/auths-pairing-daemon/src/handlers.rs|\
74+
crates/auths-pairing-daemon/src/router.rs|\
75+
crates/auths-pairing-daemon/src/error.rs)
76+
touched_handler=1 ;;
77+
docs/api-spec.yaml)
78+
touched_spec=1 ;;
79+
esac
80+
done <<< "$changed"
81+
82+
if [ "$touched_handler" = "1" ] && [ "$touched_spec" = "0" ]; then
83+
echo
84+
echo "❌ PR touches daemon handler/router/error.rs but does not update docs/api-spec.yaml."
85+
echo " Per ADR 004, update docs/api-spec.yaml in the same PR, OR have a reviewer add"
86+
echo " the 'api-no-spec-change' label if the change is provably spec-invariant."
87+
if echo "$LABELS" | grep -q '"api-no-spec-change"'; then
88+
echo " Detected 'api-no-spec-change' label — drift gate overridden."
89+
exit 0
90+
fi
91+
exit 1
92+
fi
93+
echo "✅ Spec drift gate: OK"

.spectral.yaml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Spectral ruleset for auths OpenAPI specs.
2+
#
3+
# Baseline is `spectral:oas` (the official Spectral ruleset for OAS 2/3).
4+
# Project-specific rules live below; each one is documented with a
5+
# rationale so the ruleset is self-explanatory.
6+
extends:
7+
- spectral:oas
8+
9+
rules:
10+
# Every `/v1/pairing/sessions/*` operation that is not the public
11+
# status read MUST declare at least one security scheme. A future
12+
# operation that forgets its `security:` entry is almost certainly a
13+
# bug — on a LAN-local daemon there is no plausible public endpoint
14+
# below `/v1/pairing/sessions/` beyond the status read, which is
15+
# documented in its own description.
16+
auths-security-required-on-session-paths:
17+
description: |
18+
Session-scoped endpoints (`/v1/pairing/sessions/{id}/response`,
19+
`.../confirm`, `.../confirmation`, `.../lookup`) must carry a
20+
non-empty `security` list.
21+
severity: error
22+
message: "{{path}} must declare `security` (at least one scheme)."
23+
given:
24+
- "$.paths['/v1/pairing/sessions/lookup'][get]"
25+
- "$.paths['/v1/pairing/sessions/{id}/response'][post]"
26+
- "$.paths['/v1/pairing/sessions/{id}/confirm'][post]"
27+
- "$.paths['/v1/pairing/sessions/{id}/confirmation'][get]"
28+
then:
29+
field: security
30+
function: truthy
31+
32+
# Top-level `servers` list must exist. Required for codegen tools on
33+
# the mobile side that infer the daemon's base URL from the spec.
34+
auths-servers-required:
35+
description: Top-level `servers` list must be present.
36+
severity: error
37+
message: "Spec must declare at least one entry in `servers`."
38+
given: "$"
39+
then:
40+
field: servers
41+
function: truthy

CONTRIBUTING.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,7 @@ All unit tests must pass with network blocked (`cargo test --lib --workspace` be
8383
- [ ] Public API changes documented with `Args:` and `Usage:` blocks
8484
- [ ] New port traits have a fake and/or mock in `auths-test-utils`
8585
- [ ] Breaking changes bumped version per `RELEASES.md`
86+
- [ ] If you touched `crates/auths-pairing-daemon/src/{handlers,router,error}.rs`,
87+
update `docs/api-spec.yaml` in the same PR (per ADR 004). The
88+
`api-spec` CI job enforces this; label `api-no-spec-change` can
89+
be applied by a reviewer when a change is provably spec-invariant.

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/auths-cli/src/commands/device/pair/join.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ pub(crate) async fn handle_join(
145145
device_did: pairing_response.device_did.clone(),
146146
signature: Base64UrlEncoded::from_raw(pairing_response.signature.clone()),
147147
device_name: pairing_response.device_name.clone(),
148+
subkey_chain: None,
148149
};
149150

150151
relay

crates/auths-core/src/pairing/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ mod qr;
77
// Re-export protocol types
88
pub use auths_pairing_protocol::types;
99
pub use auths_pairing_protocol::{
10-
Base64UrlEncoded, CreateSessionRequest, CreateSessionResponse, GetConfirmationResponse,
11-
GetSessionResponse, PairingResponse, PairingSession, PairingToken, ProtocolError,
12-
SessionStatus, SubmitConfirmationRequest, SubmitResponseRequest, SuccessResponse,
13-
normalize_short_code,
10+
Base64UrlEncoded, CreateSessionRequest, CreateSessionResponse, CurveTag,
11+
GetConfirmationResponse, GetSessionResponse, PairingResponse, PairingSession, PairingToken,
12+
ProtocolError, SessionStatus, SubmitConfirmationRequest, SubmitResponseRequest,
13+
SuccessResponse, normalize_short_code,
1414
};
1515

1616
// Local exports

0 commit comments

Comments
 (0)