Repository navigation
fix(agent-os): harden foundational skill-aware audit trail for issue #1609 - #2572
Conversation
🤖 AI Agent: code-reviewer — View details
TL;DR: 0 blockers, 2 warnings. The changes enhance security and auditability but introduce minor areas for follow-up.
Action items: None, as no blockers were identified. Warnings (fine as follow-up PRs):
|
🤖 AI Agent: security-scanner — View details
No security issues found. |
|
🟡 Contributor Check: MEDIUM
Automated check by AGT Contributor Check. |
🤖 AI Agent: test-generator — `agent-os/src/agent_os/integrations/base.py`
|
🤖 AI Agent: breaking-change-detector — API Compatibility
API Compatibility
|
🤖 AI Agent: docs-sync-checker — Docs Sync
Docs Sync
|
PR Review Summary
Verdict: AI review comments are untrusted advisory output. The summary reports workflow-generated completion status only, not model-authored pass/fail claims. |
Ricky Gummadi (Ricky-G)
left a comment
There was a problem hiding this comment.
Suggestion: extract the repeated trusted-source boilerplate into a BaseIntegration helper
Every adapter touched in this PR now repeats the same 9-line block, and it appears 8+ times across the diff:
trusted_skill_sources = tuple(
source
for source in (
kernel.trusted_skill_metadata_source(
skill_name=getattr(x, "skill_name", None),
skill_origin=getattr(x, "skill_origin", None),
),
)
if source is not None
)Consider extracting a small helper on BaseIntegration, e.g.:
@staticmethod
def trusted_sources_from_attrs(*objs) -> tuple[TrustedSkillMetadataSource, ...]:
return tuple(
s for s in (
BaseIntegration.trusted_skill_metadata_source(
skill_name=getattr(o, "skill_name", None),
skill_origin=getattr(o, "skill_origin", None),
) for o in objs
) if s is not None
)Each adapter call site then collapses to a single line:
trusted_skill_sources = kernel.trusted_sources_from_attrs(context)
# or for adapters that read from multiple objects:
trusted_skill_sources = kernel.trusted_sources_from_attrs(request, getattr(request, "skill_metadata", None))Why this is worth doing:
- Removes ~70+ lines of duplicated boilerplate across
autogen_adapter.py,crewai_adapter.py,google_adk_adapter.py,langchain_adapter.py,openai_agents_sdk.py, andsemantic_kernel_adapter.py. - Centralizes the trust-extraction pattern so the next adapter (or the next framework hook added to an existing adapter) can't accidentally diverge — e.g. forget the
if source is not Nonefilter, or callgetattrwithout a default. - Keeps the trust boundary enforced in exactly one place, which is the whole point of
TrustedSkillMetadataSource.
Not a blocker for merging, but high value-to-risk ratio and a natural extension of the abstraction already introduced here.
|
Thanks Ricky Gummadi (@Ricky-G) great suggestion. What I changed:
I also included small spell-check dictionary updates for intentional project terms that were blocking CI. |
Imran Siddique (imran-siddique)
left a comment
There was a problem hiding this comment.
Thanks for the contribution. The audit trail hardening changes look reasonable and well-tested (good anti-spoofing checks for skill metadata).
However, .cspell-repo-terms.txt\ contains unresolved merge conflict markers (<<<<<<<, =======, >>>>>>>). Please rebase on current main and resolve the conflicts before this can be merged.
0b59590 to
5604544
Compare
Imran Siddique (@imran-siddique) Thanks for catching this. I rebased the branch on current main, resolved the conflict markers in .cspell-repo-terms.txt, and force-pushed the updated branch. Could you please take another look when you have a chance? |
Dipika Ranabhat (qubeena07)
left a comment
There was a problem hiding this comment.
Good work on this overall. The trust boundary design is solid and the spoof resistance tests across all six adapters are exactly what this needed. A few things worth addressing before merge:
Double computation of skill fields
In autogen_adapter.py (lines ~49 to 69) and google_adk_adapter.py before_tool_callback, build_skill_audit_fields is called first to get skill_fields, and then emit_skill_audit_event is called immediately after. The problem is that emit_skill_audit_event internally calls build_skill_audit_fields again. So the fields are computed twice for the same invocation. You could either have emit_skill_audit_event accept pre-built fields, or restructure so the caller reads the returned payload for log appending instead of calling build_skill_audit_fields separately.
UTC timestamp normalization is incomplete
The PR description lists UTC timestamp normalization as one of the three blockers being fixed, and autogen_adapter.py was updated correctly. But semantic_kernel_adapter.py line 876 and langchain_adapter.py _record_tool_invocation line 598 still use datetime.now().isoformat() with no timezone info. These need the same fix.
Silent exception in hash_context
except Exception:
return NoneThe fail-safe behavior makes sense, but there is zero visibility into why a context value returned None. In production, if hashes are None across all events it will be impossible to tell whether it is by design or because payloads are consistently non-serializable. A single logger.debug call in that branch would help a lot without any real cost.
Asymmetric audit emission in google_adk_adapter.py
before_tool_callback calls both emit_skill_audit_event and _record. after_tool_callback only calls _record. This means consumers listening on GovernanceEventType.POLICY_CHECK will not receive a post-tool event from the ADK adapter, while they do from crewai (after_tool_call) and openai_agents (on_tool_end). The inconsistency across adapters for the same lifecycle position will cause surprises downstream.
provenance_source_trust as a string literal
provenance_source_trust: str | None = "trusted" if skill_name else NoneTests assert == "trusted". This works for now but if this field ever needs more granular values the string approach will cause inconsistent comparisons across consumers. A small Enum or at minimum Literal["trusted"] would make this safer with very little added complexity.
Minor observations
test_context_hash_stable_for_nested_ordering tests dict key ordering stability within list elements, not list element order stability. The name implies broader coverage than it provides. Worth a rename or a clarifying comment so someone does not assume list reordering is also stable (it is not).
The TrustedSkillMetadataSource constructible-by-anyone point is already acknowledged in the docstrings and is fine as a first-deliverable tradeoff. Just worth a TODO comment so it is not forgotten when signature-backed trust gets added later.
Overall the design is in good shape. Main blockers before merge in my view are the timestamp gap in SK and LangChain adapters, the asymmetric ADK emit, and the double computation issue.
Dipika Ranabhat (@qubeena07) Thanks for the detailed review. I’ve addressed all the points you called out in the latest update (commit 3d0cfe3):
Focused tests for touched areas pass locally. Please take another look when you have time. |
Dipika Ranabhat (qubeena07)
left a comment
There was a problem hiding this comment.
All 7 claimed changes look good. The double computation removal for AutoGen and ADK is clean, UTC normalization is correct for SK and LangChain, debug logging in the hash_context exception path is useful, the Literal["trusted"] tightening is right, and the test rename is clearer.
One question before I can sign off: line 681 in intent.py changes the verify_intent guard from checking both EXECUTING and APPROVED states to only EXECUTING. Previously an intent in APPROVED state could be verified, now it raises IntentStateError. This is a behavior change not mentioned in the PR description. Was this intentional? If so please add a note explaining why APPROVED should no longer be a valid state for verification. If not, this line should be reverted.
Also noticed base.py still has naive datetime.now() calls without UTC at lines 1303, 1345, 1432, 1453. Not a blocker for this PR but worth a follow-up to keep things consistent with the normalization done here.
1b4f9fd to
a020a1d
Compare
416277a to
18f53e3
Compare
Review status: my and Imran Siddique (@imran-siddique)'s requested changes are addressedConfirmed against the current branch:
Both of these are resolved, so I am approving and merging. Remaining feedback from Dipika Ranabhat (@qubeena07) (tracked, not blocking)Dipika Ranabhat (@qubeena07)'s points (documenting the Scope note: this is Python onlyThe skill-aware audit trail hardening here lands only in the Python Thanks Dhinesh Ponnarasan (@DhineshPonnarasan) for the thorough work and tests. |
Ricky Gummadi (Ricky-G)
left a comment
There was a problem hiding this comment.
Approving. My helper-extraction request and Imran Siddique (@imran-siddique)'s cspell conflict-marker fix are both addressed on the current branch. Dipika Ranabhat (@qubeena07)'s remaining points are tracked in #2908 and cross-language parity in #2907. This is Python only and good to merge.
Resolved: the .cspell-repo-terms.txt merge conflict markers have been fixed and main has been merged into the branch. Dismissing this stale changes-requested review so the PR can merge. Remaining non-blocking follow-ups are tracked in #2908.
Signed-off-by: Dhinesh Ponnarasan <dhineshponnarasan@gmail.com>
Signed-off-by: Dhinesh Ponnarasan <dhineshponnarasan@gmail.com>
Signed-off-by: Dhinesh Ponnarasan <dhineshponnarasan@gmail.com>
Signed-off-by: Dhinesh Ponnarasan <dhineshponnarasan@gmail.com>
Signed-off-by: Dhinesh Ponnarasan <dhineshponnarasan@gmail.com>
Head branch was pushed to by a user without write access
b633103
1bcf424 to
b633103
Compare
|
Hi Team, I investigated the reported issues, addressed the missing DCO signoff and the TODO quality-gate violation, validated the changes locally, and force-pushed the fixes. The workflow approvals and reviews appear to have been marked stale automatically after the branch update. Aside from these CI-related fixes, the implementation itself remains unchanged. Thank you again for the reviews and feedback. |
16e392c
into
microsoft:main
…regressions from #2572 * fix(agent-os): fill missing bodies in four empty if blocks in crewai_adapter Commit 16e392c added four if statements with no body, causing IndentationError at import time and failing lint, test, and docker-compose-test jobs on main. - allowed_tools check: return False when tool not in allowlist - after_tool_call pattern match: raise PolicyViolationError - before_llm_call pre_execute check: return False with log - after_llm_call pattern match: raise PolicyViolationError Signed-off-by: Imran Siddique <imran.siddique@opaque.co> * fix(agent-os): remove duplicate import blocks in google_adk, langchain, sk adapters Same commit (16e392c) left duplicate import blocks in three adapters, causing F811 ruff errors masked by the SyntaxError in crewai_adapter. - google_adk_adapter: remove redundant second 'from .base import' line - langchain_adapter: merge Callable, remove duplicate datetime/typing/.base imports - semantic_kernel_adapter: same as langchain_adapter Signed-off-by: Imran Siddique <imran.siddique@opaque.co> * fix(nexus): update test_client to use real Ed25519 keypairs for signing PR #2782 made private_key_bytes required when local_mode=True registers an agent (registry now verifies the manifest signature). Tests were not updated and failed with ValueError on every register() call. Use generate_keypair() to create a real keypair per test client and set the manifest verification_key to the matching public key. Signed-off-by: Imran Siddique <imran.siddique@opaque.co> * fix(nexus): update test_registry to use real Ed25519 keypairs for signing Registry verifies signatures on register/deregister. Tests using fake keys ("ed25519:test_key_123") and fake sigs ("sig") caused InvalidSignatureError. Replace create_test_manifest with create_signed_manifest returning a real keypair, and recompute the deregister signature as sign(private_key_bytes, agent_did.encode()). Signed-off-by: Imran Siddique <imran.siddique@opaque.co> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(nexus): wire escrow and DMZ signing in NexusClient; fix ProofOfOutcome tests client.create_escrow now passes requester_signature via _sign_escrow(). sign_dmz_policy replaced nonexistent _generate_signature with an inline sign(private_key_bytes, transfer_id.encode()) call. TestProofOfOutcome tests generate a real keypair and pass requester_signature to poo.create_escrow(), satisfying the ValueError guard added in 16e392c. Signed-off-by: Imran Siddique <imran.siddique@opaque.co> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Signed-off-by: Imran Siddique <imran.siddique@opaque.co> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Promote all 4.0.x packages to 4.1.0 to reflect the changes since the v4.0.0 release on 2026-06-01: - skill-aware audit trail hardening (#2572) - Ed25519 signature verification in Nexus registry/escrow (#2782) - ring enforcement wiring in sandbox providers (#2868) - dynamic policy conditions: time-based, cost-aware, quota-aware (#2870) - policy regression testing framework (agt test) (#2869) - crewai adapter if-body syntax fix (#2911) - various security fixes, dependabot updates Also promote agt-policies from 5.0.0a1 to 5.0.0 (alpha has been live on PyPI since the June 9 dry-run confirmed the build was clean). Signed-off-by: Imran Siddique <imran.siddique@opaque.co>
Promote all 4.0.x packages to 4.1.0 to reflect the changes since the v4.0.0 release on 2026-06-01: - skill-aware audit trail hardening (#2572) - Ed25519 signature verification in Nexus registry/escrow (#2782) - ring enforcement wiring in sandbox providers (#2868) - dynamic policy conditions: time-based, cost-aware, quota-aware (#2870) - policy regression testing framework (agt test) (#2869) - crewai adapter if-body syntax fix (#2911) - various security fixes, dependabot updates Also promote agt-policies from 5.0.0a1 to 5.0.0 (alpha has been live on PyPI since the June 9 dry-run confirmed the build was clean). Signed-off-by: Imran Siddique <imran.siddique@opaque.co>
…ization in base.py - Add CHANGELOG entry under [Unreleased] > Changed documenting that verify_intent now requires EXECUTING state only (previously APPROVED was also accepted). The lifecycle is strictly declare -> approve -> execute -> verify. Closes #2908. - Expand the guard comment in intent.py to explain why APPROVED is rejected: without an execution phase there are no records to compare against the declared plan. - Replace all 5 naive datetime.now() calls in integrations/base.py with datetime.now(timezone.utc) for consistency with UTC normalization done in the adapters (PR #2572): - ExecutionContext.start_time default factory - event_base timestamp in _run_policy_checks - elapsed-time / timeout comparison - DRIFT_DETECTED event timestamp - CHECKPOINT_CREATED event timestamp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com>
…ization in base.py (#2994) * fix: document verify_intent lifecycle narrowing and finish UTC normalization in base.py - Add CHANGELOG entry under [Unreleased] > Changed documenting that verify_intent now requires EXECUTING state only (previously APPROVED was also accepted). The lifecycle is strictly declare -> approve -> execute -> verify. Closes #2908. - Expand the guard comment in intent.py to explain why APPROVED is rejected: without an execution phase there are no records to compare against the declared plan. - Replace all 5 naive datetime.now() calls in integrations/base.py with datetime.now(timezone.utc) for consistency with UTC normalization done in the adapters (PR #2572): - ExecutionContext.start_time default factory - event_base timestamp in _run_policy_checks - elapsed-time / timeout comparison - DRIFT_DETECTED event timestamp - CHECKPOINT_CREATED event timestamp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> * fix: add fastembed to REGISTERED_PACKAGES in dep-confusion scan fastembed is a real PyPI package (fast embedding generation from Qdrant) referenced in agent-os/pyproject.toml line 61. The dep-confusion scanner was flagging it as unregistered because it was missing from REGISTERED_PACKAGES. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> * fix: add fastembed to cspell word list and clean up comment - Add 'fastembed' and 'FastEmbed' to .cspell.json words list so the spell checker does not flag the package name string and comment - Remove brand name from dep-confusion comment to avoid future spell-check churn on proper nouns Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> * fix: update tests to use timezone-aware datetimes for start_time The UTC normalization of ExecutionContext.start_time (datetime.now() -> datetime.now(timezone.utc)) broke tests that explicitly set start_time to a naive datetime. Update all four affected test files to use datetime.now(timezone.utc) so the subtraction in _run_policy_checks does not raise TypeError. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> * fix: defensive UTC normalization for naive start_time and drop out-of-scope changes Address review feedback from @imran-siddique: 1. Add defensive normalization at base.py timeout check: if ctx.start_time is naive (set by external callers before the default factory was made tz-aware), convert it to UTC via astimezone() before subtracting. astimezone() correctly interprets the naive value as local time and converts it to UTC; replace(tzinfo=...) would not adjust the value. This protects external callers that still pass naive datetimes without breaking the UTC-aware fast path. 2. Add test_blocked_when_timeout_exceeded_naive_start_time to explicitly cover the defensive normalization path with a naive start_time. 3. Revert .cspell.json and scripts/check_dependency_confusion.py to main (fastembed additions are already on main and out of scope for #2908). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> * fix: add datetimes to cspell wordlist The word 'datetimes' appears in the defensive normalization comment added in base.py and is not in the default cspell dictionary. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> --------- Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ization in base.py (microsoft#2994) * fix: document verify_intent lifecycle narrowing and finish UTC normalization in base.py - Add CHANGELOG entry under [Unreleased] > Changed documenting that verify_intent now requires EXECUTING state only (previously APPROVED was also accepted). The lifecycle is strictly declare -> approve -> execute -> verify. Closes microsoft#2908. - Expand the guard comment in intent.py to explain why APPROVED is rejected: without an execution phase there are no records to compare against the declared plan. - Replace all 5 naive datetime.now() calls in integrations/base.py with datetime.now(timezone.utc) for consistency with UTC normalization done in the adapters (PR microsoft#2572): - ExecutionContext.start_time default factory - event_base timestamp in _run_policy_checks - elapsed-time / timeout comparison - DRIFT_DETECTED event timestamp - CHECKPOINT_CREATED event timestamp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> * fix: add fastembed to REGISTERED_PACKAGES in dep-confusion scan fastembed is a real PyPI package (fast embedding generation from Qdrant) referenced in agent-os/pyproject.toml line 61. The dep-confusion scanner was flagging it as unregistered because it was missing from REGISTERED_PACKAGES. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> * fix: add fastembed to cspell word list and clean up comment - Add 'fastembed' and 'FastEmbed' to .cspell.json words list so the spell checker does not flag the package name string and comment - Remove brand name from dep-confusion comment to avoid future spell-check churn on proper nouns Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> * fix: update tests to use timezone-aware datetimes for start_time The UTC normalization of ExecutionContext.start_time (datetime.now() -> datetime.now(timezone.utc)) broke tests that explicitly set start_time to a naive datetime. Update all four affected test files to use datetime.now(timezone.utc) so the subtraction in _run_policy_checks does not raise TypeError. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> * fix: defensive UTC normalization for naive start_time and drop out-of-scope changes Address review feedback from @imran-siddique: 1. Add defensive normalization at base.py timeout check: if ctx.start_time is naive (set by external callers before the default factory was made tz-aware), convert it to UTC via astimezone() before subtracting. astimezone() correctly interprets the naive value as local time and converts it to UTC; replace(tzinfo=...) would not adjust the value. This protects external callers that still pass naive datetimes without breaking the UTC-aware fast path. 2. Add test_blocked_when_timeout_exceeded_naive_start_time to explicitly cover the defensive normalization path with a naive start_time. 3. Revert .cspell.json and scripts/check_dependency_confusion.py to main (fastembed additions are already on main and out of scope for microsoft#2908). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> * fix: add datetimes to cspell wordlist The word 'datetimes' appears in the defensive normalization comment added in base.py and is not in the default cspell dictionary. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> --------- Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: jlaportebot <jlaportebot@gmail.com>
Summary
Hardens and normalizes the foundational skill-aware audit trail for issue #1609 without expanding scope.
Problem
The initial implementation had three review blockers:
This update also tightens intent lifecycle validation so verification is only valid during active execution.
Changes
Testing
Focused regression runs:
pytest tests/test_skill_audit_helpers.py tests/test_crewai_hooks.py tests/test_semantic_kernel_hooks.py tests/test_openai_agents_sdk_adapter.py tests/test_langchain_middleware.py tests/test_autogen_hooks.py -q
Result: 224 passed
pytest tests/test_google_adk_adapter.py::TestAuditAndStats::test_audit_event_fields tests/test_google_adk_adapter.py::TestAuditAndStats::test_skill_metadata_extracted_into_audit_event tests/test_google_adk_adapter.py::TestAuditAndStats::test_spoofed_skill_metadata_in_tool_args_is_ignored -q
Result: 3 passed
Combined targeted pass of the above suites
Result: 227 passed
pytest tests/test_skill_audit_helpers.py tests/test_intent.py tests/test_intent_hardened.py tests/test_llamafirewall_adapter.py -q
Result: 98 passed
Note: Full ADK async-marked suite in this local environment still shows pre-existing async plugin configuration warnings or failures not introduced by this PR.
Scope Guardrails
This PR intentionally does not add signature systems, sandbox enforcement, skill policy engines, marketplace trust infrastructure, or heavy forensic snapshot and diff systems.
This remains first-deliverable scope: foundational, framework-agnostic, backward-compatible skill-aware audit hardening.
Follow-up
Remaining naive timestamp calls in base integration utilities will be normalized in a follow-up PR for end-to-end UTC consistency.
Closes
Closes #1609
Imran Siddique (@imran-siddique) Nishar Miya (@miyannishar) 风 (Feng) (@fengtrace) could you please review when possible?