Skip to content

security(backend): guard clone symlinks and require auth on /metrics (#182, #183) - #212

Merged
parthrohit22 merged 1 commit into
Second-Origin:devfrom
parthrohit22:fix/182-183-backend-security
Jul 27, 2026
Merged

security(backend): guard clone symlinks and require auth on /metrics (#182, #183)#212
parthrohit22 merged 1 commit into
Second-Origin:devfrom
parthrohit22:fix/182-183-backend-security

Conversation

@parthrohit22

Copy link
Copy Markdown
Collaborator

Summary

Two backend security fixes that protect the §28 "Trusted output" / custody posture: (1) the clone path no longer follows symlinks when measuring repository size (closes the symlink-escape disclosure/DoS gap), and (2) the /metrics endpoint now requires authentication instead of being world-readable.

Issue #184 was assessed during this work and is scoped out: its "sends arbitrary paths" claim is invalid (the AI context builder validates the selected file against the sealed snapshot and only ever sends snapshot-derived paths), while the omitted-review-findings half is a real but feature-sized gap with no existing review query path — tracked separately, not folded into this security PR.

Linked issue

Related to #182 (clone symlink escape) and #183 (/metrics unauthenticated). Closes #182 and #183. Note on #184 in the testing/summary above.

Roadmap alignment

  • Roadmap §23 workstream this advances: W0 (Truth contract) + W6 (Trust / private-code custody).
  • §28 market-fit criterion this moves toward: "Trusted output" and "Deployment repeatability" (no unauthenticated telemetry egress / disclosure).
  • Accepted evidence it is real: verified unauthenticated GET /metrics returned 401 after the change (TestClient smoke); full backend suite 701 passed / 4 env-skipped; OpenAPI contract test updated to assert /metrics is bearer-protected.

What changed

  • apps/backend/app/github/client.py: _directory_size uses os.walk(..., followlinks=False) and prunes symlinked directories, so a crafted symlink (e.g. to / or a parent) cannot cause disclosure or a measurement loop during clone-size budget enforcement.
  • apps/backend/app/main.py: /metrics route gains dependencies=[Depends(get_current_user)] and documents 401 in its OpenAPI responses.
  • tests/test_openapi_contract.py: /metrics removed from PUBLIC_OPERATIONS, expected statuses now {200, 401, 500}.
  • tests/test_system.py: metrics test uses auth_client and asserts 200.

Acceptance criteria completed

  • Clone size measurement does not descend into symlinked directories.
  • /metrics requires a bearer token (returns 401 unauthenticated).
  • OpenAPI contract updated and passes; backend suite green (701 passed, 4 skipped).
  • No new product surface introduced; honest-data / no-second-parser rules respected.

Testing performed

cd apps/backend && PYTHONPATH= .venv/bin/python -m pytest
# 701 passed, 4 skipped (env-gated Postgres/Redis)

Plus a TestClient smoke confirming GET /metrics -> 401 without auth.

Screenshots

Not applicable (backend/auth change).

Security and data considerations

  • /metrics now requires authentication — closes an information-disclosure surface (runtime request counters were world-readable).
  • Clone symlink guard prevents a malicious repository from escaping the clone directory during size enforcement.
  • No secrets, credentials, or repository contents are logged.

Dependencies and blocked work

None.

Scope changes or remaining work

#184 ("AI context sends arbitrary paths") is out of scope here: its security premise was found invalid on inspection (the AI context builder validates against the sealed snapshot). The omitted-review-findings aspect is a separate, larger feature (needs a review query path) and should get its own issue rather than be silently bundled.

Contributor checklist

  • This PR targets dev
  • The branch was created from an up-to-date upstream/dev
  • This PR addresses one clearly scoped issue
  • This PR advances a §23 workstream toward a §28 market-fit criterion (Roadmap alignment filled)
  • Every acceptance criterion I claim as complete is actually complete
  • Relevant tests pass
  • Documentation is updated for any user-visible change
  • No secrets, credentials, local env files, or generated artifacts are included
  • No unrelated files were changed
  • Closing syntax (Closes) is used only because the issue is fully resolved
  • Dependencies and follow-up work are linked

- github/client.py _directory_size no longer descends into symlinked
  directories (os.walk followlinks=False + prune symlink dirs), closing the
  clone-path symlink-escape disclosure/DoS gap (issue Second-Origin#182).
- /metrics now requires authentication (Depends(get_current_user)); was
  world-readable runtime metrics (issue Second-Origin#183).
- Updated OpenAPI contract + system tests to reflect /metrics as
  bearer-protected (401), matching existing contract-authority discipline.

Note on Second-Origin#184: its 'arbitrary paths' claim is invalid — RepositoryContextBuilder
validates the selected file against the sealed snapshot and only sends
snapshot-derived paths. The omitted-review-findings half is a real but
feature-sized gap (no review query path exists); tracked separately, not
folded into this security PR.
@parthrohit22
parthrohit22 merged commit 91e4cb2 into Second-Origin:dev Jul 27, 2026
8 checks passed
@parthrohit22
parthrohit22 deleted the fix/182-183-backend-security branch July 27, 2026 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security: clone path does not guard symlink escapes (filesystem disclosure + DoS)

1 participant