Skip to content

feat(apps): inject gateway origin into backends - #6519

Closed
dgomesbr wants to merge 3 commits into
kirodotdev:mainfrom
dgomesbr:feat/aidlc-gateway-origin
Closed

feat(apps): inject gateway origin into backends#6519
dgomesbr wants to merge 3 commits into
kirodotdev:mainfrom
dgomesbr:feat/aidlc-gateway-origin

Conversation

@dgomesbr

@dgomesbr dgomesbr commented Aug 28, 2026

Copy link
Copy Markdown

Problem / Motivation

backend.entryPoint apps run as separate loopback processes, so they cannot
push notifications back to the gateway (for example POST /api/notifications/push on a declared channel) without first learning the
gateway's own address. There was no safe way for a child process to discover
that address, and any app-specific, default, or request-derived value risked
pointing a child at a sibling gateway.

Why it matters

  • A backend needs a trustworthy callback base to push notifications. Handing it
    an inherited, guessed, or spoofed origin would let a child be pointed at the
    wrong gateway.
  • The per-app secret must stay owner-only. A secret that lost its mode and was
    read while still group/world-readable is a disclosure risk.
  • The change must not break existing apps: secret-less backends and in-gateway
    route apps must keep working unchanged.

What changed

Inject the gateway address at spawn time as two generic environment variables,
so no app-specific value is ever handed to a child:

  • KIROCREW_GATEWAY_ORIGIN = http://127.0.0.1:<port>, where <port> is the
    port the gateway ACTUALLY bound, via resolve_serving_port() (which prefers
    the exported KIROCREW_BOUND_PORT over an inherited or guessed
    KIROCREW_PORT). It is never the app's own PORT, a default, or a
    request-derived value, so a child can never be pointed at a sibling gateway.
  • KIROCREW_GATEWAY_ORIGIN_PROOF = HMAC-SHA256(app_secret, origin) hex,
    injected only when the app has a .app_secret. The child recomputes it with
    its secret to confirm the origin was minted by this gateway, rather than an
    inherited or spoofed env value.

Both are recomputed on every spawn, so a gateway restarted on a different port
hands the backend the current origin.

Security:

  • The per-app secret at <data-home>/apps/<name>/.app_secret is re-enforced to
    owner-only 0600 BEFORE it is read, closing the window where a secret that
    lost its mode is read while still group/world-readable. A missing secret
    stays a silent no-op; only a real lockdown failure on an existing file warns.
  • The secret is never logged. The gateway's own process environment is never
    mutated; only the child env dict is populated.
  • The proof lets a backend distinguish a gateway-minted origin from an
    inherited or spoofed env value before trusting it as a callback base.

Compatibility:

  • Backward compatible. A secret-less backend is unchanged: it still receives
    the (non-secret) origin but neither the proof nor the proxy secret.
  • In-gateway route apps (backend.routes) have no separate process and push
    in-process, so they need neither variable.
  • Docs updated: docs/app-kit/api-reference.md (Backend Environment Variables)
    and docs/system-specs/features/app-notifications.md.

Tests

  • TestGatewayOriginInjection (test/test_app_backend.py): exact origin/proof
    off the bound port (distinct from the app PORT), proof accepted under the
    right secret and rejected under a wrong key, the 0600-before-read ordering,
    missing-secret tolerance (origin only, no proof/secret), and non-entryPoint
    apps getting no injection.
  • TestAidlcNotificationsChannelOracle: pins the one-channel AI-DLC fixture
    against the imported manifest schema so a host-side schema change fails here
    rather than at app runtime.
  • Verified end to end with an isolated app on port 6777: the backend received
    the gateway origin and a valid proof and pushed a notification back on its
    declared channel. No production deploy - code, tests, and documentation only.
  • flake8 and mypy clean on the change; the 10 new tests pass.

Hand each backend.entryPoint child two generic env vars so it can call
back to THIS gateway (e.g. POST /api/notifications/push on a declared
channel), without ever learning an app-specific value.

- KIROCREW_GATEWAY_ORIGIN = http://127.0.0.1:<port>, where <port> is the
  port the gateway ACTUALLY bound via resolve_serving_port() (prefers the
  exported KIROCREW_BOUND_PORT over an inherited/guessed KIROCREW_PORT).
  Never the app's own PORT, a default, or a request-derived value, so a
  child can never be pointed at a sibling gateway.
- KIROCREW_GATEWAY_ORIGIN_PROOF = HMAC-SHA256(app_secret, origin) hex,
  injected only when a .app_secret exists. The child recomputes it to
  confirm the origin was minted by the gateway that alone holds its
  secret, rather than an inherited or spoofed env value.
- Re-enforce owner-only 0600 on .app_secret BEFORE reading it, closing
  the window where a secret that lost its mode is read while still
  group/world-readable. A missing secret stays a silent no-op (origin
  still set, no proof, no secret); only a real lockdown failure warns.

Tests (test/test_app_backend.py): TestGatewayOriginInjection asserts the
exact origin/proof off the bound port (distinct from the app PORT), proof
validity under the right secret and rejection under a wrong key, the
0600-before-read order, missing-secret tolerance, and non-entryPoint apps
getting no injection. TestAidlcNotificationsChannelOracle pins the
one-channel AI-DLC fixture against the imported manifest schema.

Docs: api-reference.md gains a Backend Environment Variables section and
app-notifications.md documents reaching the push endpoint from an
entryPoint backend.

Verified: flake8 and mypy clean on the change; the 10 new tests pass;
code, tests, and docs move together. No production deploy.
@dgomesbr
dgomesbr requested a review from a team as a code owner August 28, 2026 10:16
@dgomesbr
dgomesbr requested a review from CrysisDeu August 28, 2026 10:16
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention merge conflict Branch has merge conflicts with its base — author must resolve before merge and removed readiness: checking Automated validation is still running labels Aug 28, 2026
Merge kirodotdev/KiroCrew main (0934b80) into feat/aidlc-gateway-origin
to fix forward the CI failures that came from a stale base. The PR
branch was 47 commits behind main; the merge is conflict-free.

Validated locally on the merged tree:
- slot test test_slot_create_inherits_nearest_folder_project: pass
- test/test_app_backend.py (patch-owned): 58 passed, 4 skipped
- frontend: tsc -b clean; eslint src/ 0 errors, 664 warnings (= ceiling)
- black baseline gate pass; flake8 and mypy on backend.py clean
- semgrep custom-rule regression 11/11; diff-only SAST 0 findings

Suppressed one patch-introduced semgrep false positive
(python-logger-credential-disclosure) on the app-secret lockdown
warning: it logs the app name and the OSError only, never the secret
value. Uses the repo's standard nosemgrep convention (matches
src/kiro_crew/auth/service.py).

Remaining blocker is PR-description-owned, not code: the Fork PR
Description check needs the template sections "Problem / Motivation",
"Why it matters", and "What changed" added to the PR body.
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Aug 28, 2026
@dgomesbr

dgomesbr commented Aug 28, 2026

Copy link
Copy Markdown
Author

Ready for maintainer review and workflow approval

Update 2026-08-28: Upstream main advanced after this comment was first posted, so the earlier statement that the branch was "current with main (no rebase needed)" became stale and the PR started showing a merge conflict. That is now resolved: I merged the latest upstream main into the branch (merge commit d34e7113a, second parent upstream 66b803537), and GitHub reports the PR as mergeable again. New head: d34e7113a. Upstream has since moved a few more commits ahead of that merge base; the branch still merges cleanly, and I can re-merge if you want it strictly up to date.

All checks I can run locally are green:

  • Tests, lint, type-check, and SAST all pass on the host.
  • Fork PR Description check and Publish readiness check both pass.

Heavy CI has not run yet because it awaits maintainer workflow approval on this fork PR. I am not claiming upstream CI passed - those jobs are still pending your approval to start.

Isolated end-to-end proof (issue 6777)

To de-risk the change ahead of CI, I ran an isolated proof against a non-production, sandboxed environment:

  • Real app action -> 202 and a persisted AIDLC note.
  • Replay of the same activityId -> 200, deduplicated with no second note created.
  • Projection hash unchanged after the replay (idempotency confirmed).
  • No production deploy was performed at any point.

Request

Could a maintainer please approve the workflow so full CI can run, and take a pass on the code review? Happy to address any feedback. Thanks!

Merge origin/main (66b8035) into feat/aidlc-gateway-origin to clear the PR kirodotdev#6519 merge-conflict state. Upstream advanced past the branch's merge base; this merge makes the branch 0-behind and mergeable against the upstream base.

Merge integrity verified: index-vs-origin/main delta is exactly the gateway-origin patch (4 files, 348 insertions, 4 deletions) - src/kiro_crew/apps/backend.py, test/test_app_backend.py, docs/app-kit/api-reference.md, docs/system-specs/features/app-notifications.md. No upstream change dropped, nothing unrelated swept in, no conflict markers in text files, no unmerged paths.

Gates re-run in worktree .venv (py3.12): flake8 OK, mypy clean on backend.py, semgrep 0 findings, and the 14 merge-relevant tests (TestGatewayOriginInjection, TestAidlcNotificationsChannelOracle, TestTheCacheOnlyChildCanSeeTheCacheItMustBootFrom) pass.

Change-completeness: backend.py behavior is covered by test_app_backend.py additions (chmod-before-read invariant and positive/negative HMAC proof), and both docs match the implemented behavior.
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: checking Automated validation is still running labels Aug 28, 2026
@dgomesbr

Copy link
Copy Markdown
Author

Superseded by #6599. That PR was cut fresh from current origin/main and carries the identical net diff (4 files, +348/-4) as a single commit, which clears the PR Hygiene commit-count gate (this branch had 3 commits from two upstream merge commits, max is 2). No force-push or history rewrite was used - a new branch/PR was opened instead, per repo policy. Closing this as superseded; the branch is left intact.

@dgomesbr dgomesbr closed this Aug 28, 2026
@github-actions github-actions Bot removed the readiness: action required A blocking check or review needs attention label Aug 28, 2026
bolichen97 pushed a commit to dgomesbr/KiroCrew that referenced this pull request Sep 8, 2026
Hand each backend.entryPoint child two generic env vars so it can call
back to THIS gateway (e.g. POST /api/notifications/push on a declared
channel), without ever learning an app-specific value.

- KIROCREW_GATEWAY_ORIGIN = http://127.0.0.1:<port>, where <port> is the
  port the gateway ACTUALLY bound via resolve_serving_port() (prefers the
  exported KIROCREW_BOUND_PORT over an inherited/guessed KIROCREW_PORT).
  Never the app's own PORT, a default, or a request-derived value, so a
  child can never be pointed at a sibling gateway.
- KIROCREW_GATEWAY_ORIGIN_PROOF = HMAC-SHA256(app_secret, origin) hex,
  injected only when a .app_secret exists. The child recomputes it to
  confirm the origin was minted by the gateway that alone holds its
  secret, rather than an inherited or spoofed env value.
- Re-enforce owner-only 0600 on .app_secret BEFORE reading it, closing
  the window where a secret that lost its mode is read while still
  group/world-readable. A missing secret stays a silent no-op (origin
  still set, no proof, no secret); only a real lockdown failure warns.

Tests (test/test_app_backend.py): TestGatewayOriginInjection asserts the
exact origin/proof off the bound port (distinct from the app PORT), proof
validity under the right secret and rejection under a wrong key, the
0600-before-read order, missing-secret tolerance, and non-entryPoint apps
getting no injection. TestAidlcNotificationsChannelOracle pins the
one-channel AI-DLC fixture against the imported manifest schema.

Docs: api-reference.md gains a Backend Environment Variables section and
app-notifications.md documents reaching the push endpoint from an
entryPoint backend.

Verified on fresh origin/main (1129ef1): flake8 and mypy clean on the
change, docs lint passes, diff-only semgrep 0 findings, and
test/test_app_backend.py is 62 passed / 4 skipped. Code, tests, and docs
move together. No production deploy.

This is a single-commit replacement for PR kirodotdev#6519, which only failed the
commit-count hygiene gate (3 commits from two upstream merges); the net
diff is identical.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant